精华内容
下载资源
问答
  • 前言 前阵子我下载了基于tcp协议的客户端和服务器的代码,想借用一下来处理我们...技术涉及:socket网络编程,tcp协议,文件操作,c/c++ 软件不足:单线程,局域网,封装度低,数据结构不够精简 未来拓展:多线程,文件夹传输,

    前言

    前阵子我下载了基于tcp协议的客户端和服务器的代码,想借用一下来处理我们公司软件的日志模块,现在模块没问题了,但是我又想,服务器一般都在linux下,所以又在linux环境下写了一个服务器,也优化了客户端. 期间遇到了一些问题,都记录在这里. 如果哪里有问题,请大家指出,共同进步!

    软件名称: 文件服务器

    软件功能: 用于局域网内的文件传输

    技术涉及:socket网络编程,tcp协议,文件操作,c/c++

    软件不足:单线程,局域网,封装度低,数据结构不够精简

    未来拓展:多线程,文件夹传输,提高接口封装性,数据结构优化

    服务器代码

    本来想用纯c写,但是消息体Message用的是类,所以加了头文件<iostream>,后面的话,打算把所有的结构全部封装进对象管理类里,让程序可读性更高,也更精简.

    服务器端只完成了接受客户端上传文件,还未支持从服务器端下载文件的功能,大家可以手动添加一下

    fileserver.cpp

    //fileserver.cpp
    #include <iostream>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include "message.h"
    
    #define SERVERIP "这里写你服务器端的主机IP"
    #define PORT 10001
    #define MAX_FILE_SIZE 256
    #define MAX_BUF_SIZE 1024*10
    
    using std::cout;
    using std::endl;
    
    typedef struct sockaddr sockaddr;
    typedef struct sockaddr_in sockaddr_in;
    
    
    char gFileName[256];
    int gFileLength;
    /function declare
    void ProcessConnect(int);
    bool RecvFile(int,__MsgHead*);
    
    /fucntion define/
    bool RecvFile(int client_sock,__MsgHead* Msg){
        cout << "=======================================Enter Function:" << __FUNCTION__ << endl;
        //Start to receive file data
        FILE* pFile = fopen(gFileName,"w");
        if(!pFile){
            perror("fopen");
            return false;
        }
        char buf[MAX_BUF_SIZE];
        unsigned int i = 0;
        while(i < gFileLength - 1)
        {
            int recv_size = recv(client_sock,buf,MAX_BUF_SIZE,0);
            if(recv_size < 0){
                perror("recv_");
            }
            fwrite(buf,sizeof(char), recv_size,pFile);
            i += recv_size;
            memset(buf,0,sizeof(char)*MAX_BUF_SIZE);
        }
        fclose(pFile);
        return true;
    }
    void ProcessConnect(int new_sock){
         cout << "=======================================Enter Function:" << __FUNCTION__ << endl;
        //完成一次连接的处理
        //需要循环的来处理客户端发送的数据
        char buf[MAX_BUF_SIZE];
        while(1){
            memset(buf,0,sizeof(buf));    
            //a)从客户端读取数据
            int recv_size = recv(new_sock,buf,sizeof(buf),0);
            if(recv_size == -1)
            {
                perror("recv__");
                return;
            }
            if(recv_size == 0){
                printf("[client %d] disconnect!\n", new_sock);
            }
            __MsgHead* msgHead = (__MsgHead*)buf;
            //b)根据消息ID 响应客户端消息
            switch(msgHead->msgId){
                case MSG_FILE:
                {    
                    cout << "=======================================MSG_FILE" << endl;
                    if(!RecvFile(new_sock,(__MsgHead*)buf))
                    {
                        printf("receive file falied\n");
                        return;
                    }
                }break;
                case MSG_FILE_NAME:
                {
                    cout << "=======================================MSG_FILE_NAME" << endl;
                    __MsgFileName* msg;
                    msg = (__MsgFileName*)msgHead;
                    strcpy(gFileName,msg->fileName);
                                   
                    printf("The file name that has been received is:%s\n",gFileName);
                    
                }
                break;
                case MSG_FILE_LENGTH:
                {  
                    cout << "=======================================MSG_FILE_length" << endl;
                    __MsgFileLength* msg;
                    msg = (__MsgFileLength*)msgHead;
                    printf("The length of the file is %lld bytes\n",msg->fileLength);
                }
                break;
                case MSG_SEND_FILE:
                {
                    cout << "=======================================MSG_SEND_FILE" << endl;
                    __MsgSendFile* msg = (__MsgSendFile*)msgHead;
                    printf("[client %d] prepare to receive file from client\n", new_sock);
                }
                break;
                case MSG_DOWNLOAD_FILE:
                {
                    cout << "=======================================MSG_download_FILE" << endl;
                    printf("client wants to download files\n");
                }
                break;
            };
        }
    }
    int main(int argc, char* argv[]){
        //1.创建 socket
        int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
        if(listen_sock < 0){
            perror("socket");
            return 1;
        }
        //2.绑定端口号
        sockaddr_in server;
        server.sin_family = AF_INET;
        server.sin_addr.s_addr = inet_addr(SERVERIP);
        server.sin_port = htons(PORT);
        int n;
        setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(int));
        int ret = bind(listen_sock, (sockaddr*)&server, sizeof(server));
        if(ret < 0){
            perror("bind");
            return 1;
        }
        //3.使用 listen 允许服务器被客户端连接
        ret = listen(listen_sock, 5);
        if(ret < 0){
            perror("listen");
            return 1;
        }
        //4.服务器初始化完成,进入事件循环
        printf("Server Init OK!\n");
        while(1){
            //与客户端建立连接
            sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int new_sock = accept(listen_sock, (sockaddr*)&peer, &len);
            if(new_sock < 0){
                perror("accept");
                continue;
            }
            printf("[client %d] connect!\n", new_sock);
            ProcessConnect(new_sock);
            close(new_sock);
            sleep(1);
        }
        close(listen_sock);
        return 0;
    }
    

     

    在消息体结构里,原来的结构看起来优点臃肿,其实完全可以用一个简单的类来表示消息体,  因为c++类的成员变量是按声明顺序初始化的,所以规定第一个变量表示消息头,后面的变量再指定文件属性,客户端每次send时,比如发文件名时,赋值fileName,而其他的变量置空,这样代码就简洁明了了. 因为linux没有_int64,所以定义了一个.

    message.h

    //message.h
    #define MAX_PACK_SIZE  		10240   //数据包的长度
    #define MAX_FILE_NAME_LENGTH	256		//文件名的长度
    #define INVALID_MSG		-1      	//无效的消息
    #define MSG_FILE_LENGTH		1		//文件长度
    #define MSG_FILE_NAME		2		//文件名
    #define MSG_FILE		3		//文件内容
    #define MSG_READY		4		//准备好消息
    #define MSG_SEND_FILE		5		//发送文件
    #define MSG_DOWNLOAD_FILE	6		//下载文件
    #define MSG_COMPLETE		7		//完成信息
    
    typedef long long  _int64;
    
    class Message{
    public:
    	struct MsgHead     //头消息
    	{
    		int msgId;    //消息标识
    		MsgHead(int msg=INVALID_MSG):msgId(msg){};
    	};
    	struct MsgFileLength :public MsgHead
    	{
    		_int64 fileLength;   //文件长度
    		MsgFileLength():MsgHead(MSG_FILE_LENGTH){}
    	};
    	struct MsgFileName:public MsgHead
    	{
    		char fileName[MAX_FILE_NAME_LENGTH];
    		MsgFileName():MsgHead(MSG_FILE_NAME){}
    	};
    	struct MsgFile:public MsgHead
    	{
    		MsgFile():MsgHead(MSG_FILE){}
    	};
    	struct MsgReady:public MsgHead        //准备好消息
    	{
    		MsgReady():MsgHead(MSG_READY){}
    	};
    	struct MsgSendFile:public MsgHead  //发送文件消息
    	{
    		MsgSendFile():MsgHead(MSG_SEND_FILE){}
    	};
    	struct MsgDownLoadFile:public MsgHead     //下载文件消息
    	{
    		MsgDownLoadFile():MsgHead(MSG_DOWNLOAD_FILE){}
    	};
        struct MsgComplete:public MsgHead
    	{
    		MsgComplete():MsgHead(MSG_COMPLETE){}
    	};
    };
    
    typedef Message::MsgHead			__MsgHead;		//Id    ,msgId
    typedef Message::MsgFileLength		__MsgFileLength;//长度  ,fileLength
    typedef Message::MsgFileName		__MsgFileName;	//文件名,fileName
    typedef Message::MsgFile		__MsgFile;
    typedef Message::MsgReady		__MsgReady;
    typedef Message::MsgSendFile		__MsgSendFile;
    typedef Message::MsgDownLoadFile	__MsgDownLoadFile;
    typedef Message::MsgComplete		__MsgComplete;
    
    

    客户端在vs 2010专业版里可以正常运行

    client.cpp

    //client.cpp
    #define _CRT_SECURE_NO_WARNINGS
    #pragma once
    #include <iostream>
    #include <vector>
    #include <WinSock2.h>
    #include <string>
    #include "client.h"
    #include "message.h"
    #include <tchar.h>
    using namespace std;
    int main()
    {
    	Client client;
    
    	if(!client.InitSock())
    	{
    		cout<<"初始socket失败"<<endl;
    		return -1;
    	}
    	SOCKET saRemote=client.ConnectServer(client.ResolveAdress(SERVER_IP),PORT);
    	if(saRemote==INVALID_SOCKET)
    	{
    		cout<<"连接服务器失败"<<endl;
    		return -1;
    	}
    
    	if(!client.ProcessConnection(saRemote))
    	{
    		return -1;
    	}
    	client.CloseSocket();
    	return 0;
    }
    bool Client::InitSock()    //初始socket
    {
    	WSADATA wsData;
    	WORD wr=MAKEWORD(2,2);
    	if(WSAStartup(wr,&wsData)==0)
    	{
    		return true;
    	}
    	return false;
    }
    u_long Client::ResolveAdress(char *serverIp)   //解析IP地址
    {
    	u_long nAddr=inet_addr(serverIp);
    	if(nAddr==INADDR_NONE)  //表明serverIp使用的是主机名形式
    	{
    		hostent *ent=gethostbyname(serverIp);
    		if(ent==NULL)
    		{
    			cout<<"获取主机名出错"<<WSAGetLastError()<<endl;
    		}
    		else
    		{
    			nAddr=*((u_long *)ent->h_addr_list[0]);
    		}
    	}
    	if(nAddr==INADDR_NONE)
    	{
    		cout<<"解析主机地址失败"<<endl;
    	}
    	return nAddr;
    }
    SOCKET Client::ConnectServer(u_long serverIp,int port)   //连接服务器
    {
    	sd=socket(AF_INET,SOCK_STREAM,0);
    	if(sd==INVALID_SOCKET)
    	{
    		cout<<"床架套接字失败"<<endl;
    		return INVALID_SOCKET;
    	}
    	sockaddr_in saServer;
    	saServer.sin_family=AF_INET;
    	saServer.sin_addr.S_un.S_addr=serverIp;
    	saServer.sin_port=htons(port);
    	if(connect(sd,(sockaddr*)&saServer,sizeof(sockaddr_in))==SOCKET_ERROR)
    	{
    		cout<<"连接服务器失败"<<WSAGetLastError()<<endl;
    		closesocket(sd);
    		getchar();
    		return INVALID_SOCKET;
    	}
    	return sd;
    }
    bool Client::ProcessConnection(SOCKET sd)      //进行通信
    {
    	//-------------------------------------------------
    	//可以将下面代码看做设置系统缓冲区
    	int nRecvBuf=1024000;//设置为1000K
        setsockopt(sd,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
          //发送缓冲区
        int nSendBuf=1024000;//设置为1000K
        setsockopt(sd,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
    	//---------------------------------------------------------
    	while(true)
    	{
    		cout<<"1 UPLOAD file to Server"<<endl;
    		cout<<"2 DOWNLOAD file from Server"<<endl;
    		cout<<"3 Quit"<<endl;
    		int n;
    		cin.clear();
    		cin.sync();
    		cin>>n;
    		switch(n)
    		{
    		case 1:
    			{
    				//向服务器发送传送文件消息
    				Message::MsgSendFile msgSendFile;
    				if(send(sd,(char *)&msgSendFile,sizeof(Message::MsgSendFile),0)==SOCKET_ERROR)
    				{
    					cout<<"发送消息失败"<<endl;
    					return false;
    				}
    				Sleep(10);     //睡眠10ms保证对方将发送的消息取走
    				char filePath[MAX_FILE_NAME_LENGTH];
    				cout<<"Enter Full path like F:\\a\\b.jpg"<<endl;	
    				FILE* pf = NULL;
    				while(1)
    				{
    					cin.clear();
    					fflush(stdin);
    					cin>>filePath;
    					pf = fopen(filePath,"r");
    					if(!pf)
    					{
    						cout << "the path is invalid, enter a new path again!" << endl;
    						pf = NULL;
    						filePath[0] = '\0';
    					}else{
    						fclose(pf);
    						break;
    					}
    				}
    				printf("%s\n",filePath);
    				char fileDrive[_MAX_DRIVE];
    				char fileDir[_MAX_DIR];
    				char fileName[_MAX_FNAME];
    				char fileExt[_MAX_EXT];
    				_splitpath(filePath,fileDrive,fileDir,fileName,fileExt);  //将文件路径解析
    				Message::MsgFileName msgFileName;
    				strcat(fileName,fileExt);//abc.jpg
    				strcpy(msgFileName.fileName, fileName);
    				if(send(sd,(char *)&msgFileName,sizeof(Message::MsgFileName),0)==SOCKET_ERROR)  //发送文件名
    				{
    					cout<<"发送文件名出错"<<WSAGetLastError()<<endl;
    				}
    				Sleep(10);
                    if(!SendFileLength(sd,filePath))  //发送文件长度
    				{
    					cout<<"发送文件长度出错"<<endl;
    					return false;
    				}
    				Sleep(10);
    				if(!SendFile(sd,filePath))  //发送文件
    				{
    					cout<<"发送文件出错"<<endl;
    					return false;
    				}
    			}
    			break;
    		case 2:
    			{
    				Message::MsgDownLoadFile msgDownLoadFile;
    				if(send(sd,(char *)&msgDownLoadFile,sizeof(Message::MsgDownLoadFile),0)==SOCKET_ERROR)
    				{
    					cout<<"发送下载文件消息失败"<<WSAGetLastError()<<endl;
    					return false;
    				}
    				if(!RecvCatalogInfo(sd))
    				{
    					return false;
    				}
    				if(!SendDownLoadFileName(sd))
    				{
    					return false;
    				}
    				if(!ReceiveFileLength(sd))
    				{
    					return false;
    				}
    				if(!ReceiveFileName(sd))
    				{
    					return false;
    				}
    				if(!ReceiveFile(sd))
    				{
    					return false;
    				}
    			}
    			break;
    		case 3:
    			exit(0);
    			break;
    		default:
    			cout<<"你输入的不符合要求,重新输入"<<endl;
    		}
    	}
    	return true;
    }
    bool Client::SendFileLength(SOCKET sd,char *filePath)
    {
    	
    	FILE *pFile = fopen(filePath,"r+");
    	if(!pFile)
    	{
    		cout<<"打开文件失败"<<WSAGetLastError() << "尝试创建新文件"<<endl;
    		//return false;
    		pFile = fopen(filePath,"wb");
    		if(!pFile)
    		{
    			return false;
    		}
    	}
    	fseek(pFile,0,SEEK_END);
    	nFileLength=_ftelli64(pFile);
    	Message::MsgFileLength msgFileLength;
    	msgFileLength.fileLength=nFileLength;
    	//if(pFile)
    		fclose(pFile);
    	if(send(sd,(char *)&msgFileLength,sizeof(Message::MsgFileLength),0)==SOCKET_ERROR)
    	{
    		return false;
    	}
    	return true;
    }
    bool Client::SendFile(SOCKET sd,char *filePath)   //发送文件(绝对路径)
    {
    	cout<<"进入到发送文件内容"<<endl;
    	Message::MsgFile msgFile;
    	if(send(sd,(char *)&msgFile,sizeof(Message::MsgFile),0)==SOCKET_ERROR)
    	{
    	    cout<<"发送文件消息出错"<<WSAGetLastError()<<endl;
    		return false;
    	}
    	Sleep(10);
    	FILE *pFile;
    	pFile=fopen(filePath,"r+b");
    	fseek(pFile,0,SEEK_SET);   //定位到文件首位置
    	_int64 i=0;
    	char buff[MAX_PACK_SIZE];
    	//int nSend=0;
    	while(i<nFileLength)
    	{
    		int nSize;
    		if(MAX_PACK_SIZE > nFileLength - i)
    			nSize = nFileLength - i;
    		else
    			nSize = MAX_PACK_SIZE - 1;
    		
    		fread(buff,sizeof(char),nSize/*(int)(nFileLength-nSend)*/,pFile);
    		int nSend = send(sd,buff,nSize /*nFileLength-nSend*/,0);
    		if(nSend==SOCKET_ERROR)
    		{
    			cout<<"发送失败"<<endl;
    			return false;
    		}
    		i += nSend;
    		//fseek(pFile,i,SEEK_SET);
    		fseek(pFile,(long)i,SEEK_SET);  //定位到实际已发送到的位置
    		memset(buff,0,sizeof(char)*MAX_PACK_SIZE); //将buff清空
    	}
    	fclose(pFile);
    	return true;
    }
    bool Client::RecvCatalogInfo(SOCKET sd)   //接收目录信息
    {
    	int flag=1;     //接收目录信息成功标志
    	char buff[MAX_PACK_SIZE];
    	Message::MsgHead *msgHead;
        while(true)
    	{
    		if(recv(sd,buff,MAX_PACK_SIZE,0)==SOCKET_ERROR)
    		{
    			cout<<"接收目录信息失败"<<WSAGetLastError()<<endl;
    			flag=0;
    			break;
    		}
    		msgHead=(Message::MsgHead *)buff;
    		if(msgHead->msgId==MSG_COMPLETE)      //判断消息是否是标准消息
    		{
    			cout<<"目录信息发送完成"<<endl;
    			break;
    		}
    		else
    		{
    			cout<<buff<<endl;     //发送来的是目录信息,即文件名
    		}
    	}
    	if(flag==0)
    	{
    		return false;
    	}
    	return true;
    }
    bool Client::SendDownLoadFileName(SOCKET sd)      //发送下载的文件名
    {
    	cout<<"请输入你要下载的文件名"<<endl;
    	char fileName[_MAX_FNAME+_MAX_EXT];
    	cin>>fileName;
    	Message::MsgFileName msgFileName;
    	strcpy(msgFileName.fileName,fileName);
    	if(send(sd,(char *)&msgFileName,MAX_PACK_SIZE,0)==SOCKET_ERROR)
    	{
    		cout<<"发送下载文件名出错"<<WSAGetLastError()<<endl;
    		return false;
    	}
    	return true;
    }
    bool Client::ReceiveFileLength(SOCKET sd)     //接收下载的文件长度
    {
    	char buff[MAX_PACK_SIZE];
    	Message::MsgFileLength *msgFileLength;
    	if(recv(sd,buff,MAX_PACK_SIZE,0)==SOCKET_ERROR)
    	{
    		cout<<"接收文件长度失败"<<WSAGetLastError()<<endl;
    		return false;
    	}
    	msgFileLength=(Message::MsgFileLength *)buff;
    	nFileLength=msgFileLength->fileLength;
    	cout<<"接收到文件长度"<<nFileLength<<endl;
    	return true;
    }
    bool Client::ReceiveFileName(SOCKET sd)   //接收下载的文件名
    {
    	char buff[MAX_PACK_SIZE];
    	memset(fileName,0,sizeof(char)*(_MAX_FNAME+_MAX_EXT));
    	Message::MsgFileName *msgFileName;
    	if(recv(sd,buff,MAX_PACK_SIZE,0)==SOCKET_ERROR)
    	{
    		cout<<"接收文件名出错"<<endl;
    		return false;
    	}
    	msgFileName=(Message::MsgFileName *)buff;
    	strcpy(fileName,msgFileName->fileName);
    	cout<<"接收到文件名"<<fileName<<endl;
    	return true;
    }
    bool Client::ReceiveFile(SOCKET sd)      //接收文件内容
    {
       char buff[MAX_PACK_SIZE];
       /*
       FILE *pFile;
       pFile=fopen(fileName,"a+b");
       */
       char* strPath = "D:\\图片\\";
       char* strExt = ".jpg";
       strcat(strPath,strcat(fileName,strExt));
       FILE* pFile = fopen(strPath,"a+b");
    
       _int64 i=0;
       while(i+1<nFileLength)
       {
    		int nRecv=recv(sd,buff,MAX_PACK_SIZE,0);
            if(nRecv==SOCKET_ERROR)
    		{
    		    return false;
    		}
    		fwrite(buff,sizeof(char),nRecv,pFile);
    		i+=nRecv;
    	    memset(buff,0,sizeof(char)*MAX_PACK_SIZE);
    	}
    	fclose(pFile);
    	return true;
    }
    void Client::CloseSocket()   //关闭套接字
    {
    	closesocket(sd);
    	WSACleanup();
    }
    
    TCHAR* Client::GetCurDir(){
    	TCHAR szFilePath[MAX_PATH + 1]={0};
    	GetModuleFileName(NULL, szFilePath, MAX_PATH);
    	(_tcsrchr(szFilePath, _T('\\')))[0] = 0; // 删除文件名,只获得路径字串
    	return szFilePath;  // 例如str_url==e:\program\Debug
    	
    }

    client.h

    //client.h
    #pragma once
    #include<iostream>
    #include<fstream>
    #include<vector>
    #include<WinSock2.h>
    #pragma comment(lib,"Ws2_32.lib")
    using namespace std;
    #define SERVER_IP "这里写服务器端的IP"
    #define PORT  10001
    class Client
    {
    public:
    	_int64 nFileLength;
    	char fileName[_MAX_FNAME+_MAX_EXT];
    	SOCKET sd;
    	bool InitSock();   //初始化winsock
    	u_long ResolveAdress(char *serverIp);    //解析服务器地址
    	SOCKET ConnectServer(u_long serverIp,int port);//连接服务器
    	bool ProcessConnection(SOCKET sd);    //客户端服务器交互
    	void CloseSocket();         //释放套接字
    	bool SendFileLength(SOCKET sd,char *filePath);  //发送文件长度
    	bool SendFile(SOCKET sd,char *filePath);    //发送文件
    	bool RecvCatalogInfo(SOCKET sd);     //接收目录信息
    	bool SendDownLoadFileName(SOCKET sd);  //发送要下载的文件名
    	bool ReceiveFileLength(SOCKET sd);    //接收文件长度
    	bool ReceiveFileName(SOCKET sd);   //接收文件名
    	bool ReceiveFile(SOCKET sd);      //接收文件
    	TCHAR* GetCurDir();
    	//void DoWork();       //主体函数
    };

    客户端输入微信安装包的路径

     

    服务器后来显示如下,大小也对应

    root@ley:~/myStudy/server$ ls
    ?????    a.out    getpwd.c   ser.c    server_version  WeChatSetup.exe
    abc.txt  def.txt  message.h  ser.cpp  sss.cpp

    说下遇到的几个新手坑:

    1 recv会报错.有人说客户端请求数据为0字节时,说明已经关闭连接了,但是这时候如果在 if(recv_data_size == 0)里关闭socket,recv会报错

    2 地址重用很有用

    3 send和recv顺序对应,如果while里先发送fileName ,然后fileLength, 接收时如果先接收长度再名字,就会报错.

    4 WSAGetLastError()很好用

    展开全文
  • 网络编程基本概念说起 我们常常使用HTTP协议来传输各种格式的数据,其实HTTP这个应用层协议的底层,是基于传输层TCP协议来实现的。TCP协议仅仅把这些数据当做一串无意义的数据流来看待。所以,我们可以说:...

    从网络编程基本概念说起

          我们常常使用HTTP协议来传输各种格式的数据,其实HTTP这个应用层协议的底层,是基于传输层TCP协议来实现的。TCP协议仅仅把这些数据当做一串无意义的数据流来看待。所以,我们可以说:客户端与服务器通过在建立的连接上发送字节流来进行通信。
          这种C/S架构的通信机制,需要标识通信双方的网络地址和端口号信息。对于客户端来说,需要知道我的数据接收方位置,我们用网络地址和端口来唯一标识一个服务端实体;对于服务端来说,需要知道数据从哪里来,我们同样用网络地址和端口来唯一标识一个客户端实体。那么,用来唯一标识通信两端的数据结构就叫做套接字。一个连接可以由它两端的套接字地址唯一确定:

    (客户端地址:客户端端口号,服务端地址:服务端端口号)

    有了通信双方的地址信息之后,就可以进行数据传输了。那么我们现在需要一个规范,来规定通信双方的连接及数据传输过程。在Unix系统中,实现了一套套接字接口,用来描述和规范双方通信的整个过程。

    socket():创建一个套接字描述符

    connect():客户端通过调用connect函数来建立和服务器的连接

    bind():告诉内核将socket()创建的套接字与某个服务端地址与端口连接起来,后续会对这个地址和端口进行监听

    listen():告诉内核,将这个套接字当成服务器这种被动实体来看待(服务器是等待客户端连接的被动实体,而内核认为

    socket()创建的套接字默认是主动实体,所以才需要listen()函数,告诉内核进行主动到被动实体的转换)

    accept():等待客户端的连接请求并返回一个新的已连接描述符

    最简单的单进程服务器

          由于Unix的历史遗留问题,原始的套接字接口对地址和端口等数据封装并不简洁,为了简化这些我们不关注的细节而只关注整个流程,我们使用PHP来进行分析。PHP对Unix的socket相关接口进行了封装,所有相关套接字的函数都被加上了socket_前缀,并且使用一个资源类型的套接字句柄代替Unix中的文件描述符fd。在下文的描述中,均用“套接字”代替Unix中的文件描述符fd进行阐述。一个PHP实现的简单服务器伪代码如下:

    <?php
    
    if (($listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))=== false) {
        echo '套接字创建失败';
    }
    if (socket_bind($listenSocket, '127.0.0.1', 8888) === false) {
        echo '绑定地址与端口失败';
    }
    if (socket_listen($listenSocket) === false) {
        echo '转换主动套接字到被动套接字失败';
    }
    while (1) {
        if (($connSocket = socket_accept($listenSocket)) === false) {
            echo '客户端的连接请求还没有到达';
        } else {
            socket_close($listenSocket); //释放监听套接字
            socket_read($connSocket);  //读取客户端数据,阻塞
            socket_write($connSocket); //给客户端返回数据,阻塞
            
        }
        socket_close($connSocket);
    }

    我们梳理一下这个简单的服务器创建流程

        socket_create():创建一个套接字,这个套接字就代表建立的连接上的一个端点。第一个参数AF_INET为使用的底层协议为IPv4;第二个参数SOCK_STREAM表示使用字节流进行数据传输;第三个参数SQL_TCP代表本层协议为TCP协议。这里创建的套接字只是一个连接上的端点的一个抽象概念。

        socket_bind():绑定这个套接字到一个具体的服务器地址和端口上,真正实例化这个套接字。参数就是你之前创建的一个抽象的套接字,还有你具体的网络地址和端口。

        socket_listen():我们观察到只有一个函数参数就是之前创建的套接字。有些同学之前可能认为这一步函数调用完全没有必要。但是它告诉内核,我是一个服务器,将套接字转换为一个被动实体,其实是有很大的作用的。

        socket_accept():接收客户端发来的请求。因为服务器启动之后,是不知道客户端什么时候有连接到来的。所以,需要在一个while循环中不断调用这个函数,如果有连接请求到来,那么就会返回一个新的套接字,我们可以通过这个新的套接字进行与客户端的数据通信,如果没有,就只能不断地进行循环,直到有请求到来为止。

    注意,在这里我将套接字分为两类,一个是监听套接字,一个是连接套接字。注意这里对两种套接字的区分,在下面的讨论中会用到:

    监听套接字:服务器对某个端口进行监听,这个套接字用来表示这个端口($listenSocket)

    连接套接字:服务器与客户端已经建立连接,所有的读写操作都要在连接套接字上进行($connSocket)

    那么我们对这个服务器进行分析,它存在什么问题呢?

        一个这样的服务器进程只能同时处理一个客户端连接与相关的读写操作。因为一旦有一个客户端连接请求到来,我们对监听套接字进行accept之后,就开启了与该客户端的数据传输过程。在数据读写的过程中,整个进程被该客户端连接独占,当前服务器进程只能处理该客户端连接的读写操作,无法对其它客户端的连接请求进行处理。

    IO并发性能提升之路

    由于上述服务器的性能太烂,无法同时处理多个客户端连接以及读写操作,所以优秀的开发者们想出了以下几种方案,用以提升服务器的效率,分别是:

    多进程多线程基于单进程的IO多路复用(select/poll/epoll)

    多进程

    那么如何去优化单进程呢?很简单,一个进程不行,那搞很多个进程不就可以同时处理多个客户端连接了吗?我们想了想,写出了代码:

    <?php
    
    if (($listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))=== false) {
        echo '套接字创建失败';
    }
    if (socket_bind($listenSocket, '127.0.0.1', 8888) === false) {
        echo '绑定地址与端口失败';
    }
    if (socket_listen($listenSocket) === false) {
        echo '转换主动套接字到被动套接字失败';
    }
    for ($i = 0; $i < 10; $i++) { //初始创建10个子进程
        if (pcntl_fork() == 0) {
            if (($connSocket = socket_accept($listenSocket)) === false) {
                echo '客户端的连接请求还没有到达';
            } else {
                socket_close($listenSocket); //释放监听套接字
                socket_read($connSocket);  //读取客户端数据
                socket_write($connSocket); //给客户端返回数据
            }
            socket_close($connSocket);
        }
    }

          我们主要关注这个for循环,一共循环了10次代表初始的子进程数量我们设置为10。接着我们调用了pcntl_fork()函数创建子进程。由于一个客户端的connect就对应一个服务端的accept。所以在每个fork之后的10个子进程中,我们均进行accept的系统调用,等待客户端的连接。这样,就可以通过10个服务器进程,同时接受10个客户端的连接、同时为10个客户端提供读写数据服务。
          注意这样一个细节,由于所有子进程都是预先创建好的,那么请求到来的时候就不用创建子进程,也提高了每个连接请求的处理效率。同时也可以借助进程池的概念,这些子进程在处理完连接请求之后并不立即回收,可以继续服务下一个客户端连接请求,就不用重复的进行fork()的系统调用,也能够提高服务器的性能。这些小技巧在PHP-FPM的实现中都有所体现。其实这种进程创建方式是其三种运行模式中的一种,被称作static(静态进程数量)模式:

        ondemand:按需启动。PHP-FPM启动的时候不会启动任何一个子进程(worker进程),只有客户端连接请求到达时才启动

        dynamic:在PHP-FPM启动时,会初始启动一些子进程,在运行过程中视情况动态调整worker数量

        static:PHP-FPM启动时,启动固定大小数量的子进程,在运行期间也不会扩容

    回到正题,多进程这种方式的的确确解决了服务器在同一时间只能处理一个客户端连接请求的问题,但是这种基于多进程的客户端连接处理模式,仍存在以下劣势:

        fork()等系统调用会使得进程的上下文进行切换,效率很低

        进程创建的数量随着连接请求的增加而增加。比如100000个请求,就要fork100000个进程,开销太大

        进程与进程之间的地址空间是私有、独立的,使得进程之间的数据共享变得困难

    既然谈到了多进程的数据共享与切换开销的问题,那么我们能够很快想到解决该问题的方法,就是化多进程为更轻量级的多线程。

    对于想从事Linux后端服务器开发的或者是想转行互联网行业的朋友,提升自己,通过耗时比较久,技术栈不完善,架构不成体系,自律性差。这里给大家分享一个系统的学习视频,点击链接订阅后免费试听:https://ke.qq.com/course/417774?flowToken=1013189

    从基础入门开始到后端高级架构,一个完整的体系课程,原理到实战的讲解,值得推荐,有兴趣了解的朋友可以看看哦。

    多线程

         线程是运行在进程上下文的逻辑流。一个进程可以包含多个线程,多个线程运行在单一的进程上下文中,因此共享这个进程的地址空间的所有内容,解决了进程与进程之间通信难的问题。同时,由于一个线程的上下文要比一个进程的上下文小得多,所以线程的上下文切换,要比进程的上下文切换效率高得多。线程是轻量级的进程,解决了进程上下文切换效率低的问题。
         由于PHP中没有多线程的概念,所以我们仅仅把上面的伪代码中创建进程的部分,改成创建线程即可,代码大体类似,在此不再赘述。

    IO多路复用

    前面谈到的都是通过增加进程和线程的数量来同时处理多个套接字而IO多路复用只需要一个进程就能够处理多个套接字。IO多路复用这个名词看起来好像很复杂很高深的样子。实际上,这项技术所能带来的本质成果就是:一个服务端进程可以同时处理多个套接字描述符。

    多路:多个客户端连接(连接就是套接字描述符)

    复用:使用单进程就能够实现同时处理多个客户端的连接

    在之前的讲述中,一个服务端进程,只能同时处理一个连接。如果想同时处理多个客户端连接,需要多进程或者多线程的帮助,免不了上下文切换的开销。

    IO多路复用技术就解决了上下文切换的问题IO多路复用技术的发展可以分为select->poll->epoll三个阶段。

    IO多路复用的核心就是添加了一个套接字集合管理员,它可以同时监听多个套接字。由于客户端连接以及读写事件到来的随机性,我们需要这个管理员在单进程内部对多个套接字的事件进行合理的调度。

    select

          最早的套接字集合管理员是select()系统调用,它可以同时管理多个套接字。select()函数会在某个或某些套接字的状态从不可读变为可读、或不可写变为可写的时候通知服务器主进程。所以select()本身的调用是阻塞的。但是具体哪一个套接字或哪些套接字变为可读或可写我们是不知道的,所以我们需要遍历所有select()返回的套接字来判断哪些套接字可以进行处理了。而这些套接字中又可以分为监听套接字与连接套接字(上文提过)。我们可以使用PHP为我们提供的socket_select()函数。在select()的函数原型中,为套接字们分了个类:读、写与异常套接字集合,分别监听套接字的读、写与异常事件。:

    function socket_select (array &$read, array &$write, array &$except, $tv_sec, $tv_usec = 0) {}

         举个例子,如果某个客户单通过调用connect()连接到了服务器的监听套接字($listenSocket)上,这个监听套接字的状态就会从不可读变为可读。由于监听套接字只有一个,select()对于监听套接字上的处理仍然是阻塞的。一个监听套接字,存在于整个服务器的生命周期中,所以在select()的实现中并不能体现出其对监听套接字的优化管理。
         在当一个服务器使用accept()接受多个客户端连接,并生成了多个连接套接字之后,select()的管理才能就会体现出来。这个时候,select()的监听列表中有一个监听套接字、和与一堆客户端建立连接后新创建的连接套接字。在这个时候,可能这一堆已建立连接的客户端,都会通过这个连接套接字发送数据,等待服务端接收。假设同时有5个连接套接字都有数据发送,那么这5个连接套接字的状态都会变成可读状态。由于已经有套接字变成了可读状态,select()函数解除阻塞,立即返回。具体哪一个套接字或哪些套接字变为可读或可写我们是不知道的,所以我们需要遍历所有select()返回的套接字,来判断哪些套接字已经就绪,可以进行读写处理。遍历完毕之后,就知道有5个连接套接字可以进行读写处理,这样就实现了同时对多个套接字的管理。使用PHP实现select()的代码如下:

    <?php
    if (($listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP))=== false) {
        echo '套接字创建失败';
    }
    if (socket_bind($listenSocket, '127.0.0.1', 8888) === false) {
        echo '绑定地址与端口失败';
    }
    if (socket_listen($listenSocket) === false) {
        echo '转换主动套接字到被动套接字失败';
    }
    
    /* 要监听的三个sockets数组 */
    $read_socks = array(); //读
    $write_socks = array(); //写
    $except_socks = NULL; //异常
    
    $read_socks[] = $listenSocket; //将初始的监听套接字加入到select的读事件监听数组中
    
    while (1) {
        /* 由于select()是引用传递,所以这两个数组会被改变,所以用两个临时变量 */
        $tmp_reads = $read_socks;
        $tmp_writes = $write_socks;
        $count = socket_select($tmp_reads, $tmp_writes, $except_socks, NULL);
        foreach ($tmp_reads as $read) { //不知道哪些套接字有变化,需要对全体套接字进行遍历来看谁变了
            if ($read == $listenSocket) { //监听套接字有变化,说明有新的客户端连接请求到来
                $connSocket = socket_accept($listenSocket);  //响应客户端连接, 此时一定不会阻塞
                if ($connSocket) {
                    //把新建立的连接socket加入监听
                    $read_socks[] = $connSocket;
                    $write_socks[] = $connSocket;
                }
            } else { //新创建的连接套接字有变化
                /*客户端传输数据 */
                $data = socket_read($read, 1024);  //从客户端读取数据, 此时一定会读到数据,不会产生阻塞
                if ($data === '') { //已经无法从连接套接字中读到数据,需要移除对该socket的监听
                    foreach ($read_socks as $key => $val) {
                        if ($val == $read) unset($read_socks[$key]); //移除失效的套接字
                    }
                    foreach ($write_socks as $key => $val) {
                        if ($val == $read) unset($write_socks[$key]);
                    }
                    socket_close($read);
                } else { //能够从连接套接字读到数据。此时$read是连接套接字
                    if (in_array($read, $tmp_writes)) {
                        socket_write($read, $data);//如果该客户端可写 把数据写回到客户端
                    }
                }
            }
        }
    }
    socket_close($listenSocket);

    但是,select()函数本身的调用阻塞的。因为select()需要一直等到有状态变化的套接字之后(比如监听套接字或者连接套接字的状态由不可读变为可读),才能解除select()本身的阻塞,继续对读写就绪的套接字进行处理。虽然这里是阻塞的,但是它能够同时返回多个就绪的套接字,而不是之前单进程中只能够处理一个套接字,大大提升了效率
    总结一下,select()的过人之处有以下几点:

    实现了对多个套接字的同时、集中管理

    通过遍历所有的套接字集合,能够获取所有已就绪的套接字,对这些就绪的套接字进行操作不会阻塞

    但是,select()仍存在几个问题:

        select管理的套接字描述符们存在数量限制。在Unix中,一个进程最多同时监听1024个套接字描述符

        select返回的时候,并不知道具体是哪个套接字描述符已经就绪,所以需要遍历所有套接字来判断哪个已经就绪,可以继续进行读写

    为了解决第一个套接字描述符数量限制的问题,聪明的开发者们想出了poll这个新套接字描述符管理员,用以替换select这个老管理员,select()就可以安心退休啦。

    poll

    poll解决了select带来的套接字描述符的最大数量限制问题。由于PHP的socket扩展没有poll对应的实现,所以这里放一个Unix的C语言原型实现:

    int poll (struct pollfd *fds, unsigned int nfds, int timeout);

          poll的fds参数集合了select的read、write和exception套接字数组,合三为一。poll中的fds没有了1024个的数量限制。当有些描述符状态发生变化并就绪之后,poll同select一样会返回。但是遗憾的是,我们同样不知道具体是哪个或哪些套接字已经就绪,我们仍需要遍历套接字集合去判断究竟是哪个套接字已经就绪,这一点并没有解决刚才提到select的第二个问题。
         我们可以总结一下,select和poll这两种实现,都需要在返回后,通过遍历所有的套接字描述符来获取已经就绪的套接字描述符。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。
    为了解决不知道返回之后究竟是哪个或哪些描述符已经就绪的问题,同时避免遍历所有的套接字描述符,聪明的开发者们又发明出了epoll机制,完美解决了select和poll所存在的问题。

    epoll

    epoll是最先进的套接字们的管理员,解决了上述select和poll中所存在的问题。它将一个阻塞的select、poll系统调用拆分成了三个步骤。一次select或poll可以看作是由一次 epoll_create、若干次 epoll_ctl、若干次 epoll_wait构成:

    int epoll_create(int size);
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

        epoll_create():创建一个epoll实例。后续操作会使用

        epoll_ctl():对套接字描述符集合进行增删改操作,并告诉内核需要监听套接字描述符的什么事件

        epoll_wait():等待监听列表中的连接事件(监听套接字描述符才会发生)或读写事件(连接套接字描述符才会发生)。如果有某个或某些套接字事件已经准备就绪,就会返回这些已就绪的套接字们

    看起来,这三个函数明明就是从select、poll一个函数拆成三个函数了嘛。我们对某套接字描述符的添加、删除、修改操作由之前的代码实现变成了调用epoll_ctl()来实现。epoll_ctl()的参数含义如下:

    epfd:epoll_create()的返回值

    op:表示对下面套接字描述符fd所进行的操作。EPOLL_CTL_ADD:将描述符添加到监听列表;EPOLL_CTL_DEL:不再监听某描述符;EPOLL_CTL_MOD:修改某描述符

    fd:上面op操作的套接字描述符对象(之前在PHP中是listenSocket与listenSocket与        connSocket两种套接字描述符)例如将某个套接字添加到监听列表中

    event:告诉内核需要监听该套接字描述符的什么事件(如读写、连接等)

         最后我们调用epoll_wait()等待连接或读写等事件,在某个套接字描述符上准备就绪。当有事件准备就绪之后,会存到第二个参数epoll_event结构体中。通过访问这个结构体就可以得到所有已经准备好事件的套接字描述符。这里就不用再像之前select和poll那样,遍历所有的套接字描述符之后才能知道究竟是哪个描述符已经准备就绪了,这样减少了一次O(n)的遍历,大大提高了效率。
         在最后返回的所有套接字描述符中,同样存在之前说过的两种描述符:监听套接字描述符和连接套接字描述符。那么我们需要遍历所有准备就绪的描述符,然后去判断究竟是监听还是连接套接字描述符,然后视情况做做出accept(监听套接字)或者是read(连接套接字)的处理。一个使用C语言编写的epoll服务器的伪代码如下(重点关注代码注释):

    int main(int argc, char *argv[]) {
    
        listenSocket = socket(AF_INET, SOCK_STREAM, 0); //同上,创建一个监听套接字描述符
        
        bind(listenSocket)  //同上,绑定地址与端口
        
        listen(listenSocket) //同上,由默认的主动套接字转换为服务器适用的被动套接字
        
        epfd = epoll_create(EPOLL_SIZE); //创建一个epoll实例
        
        ep_events = (epoll_event*)malloc(sizeof(epoll_event) * EPOLL_SIZE); //创建一个epoll_event结构存储套接字集合
        event.events = EPOLLIN;
        event.data.fd = listenSocket;
        
        epoll_ctl(epfd, EPOLL_CTL_ADD, listenSocket, &event); //将监听套接字加入到监听列表中
        
        while (1) {
        
            event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); //等待返回已经就绪的套接字描述符们
            
            for (int i = 0; i < event_cnt; ++i) { //遍历所有就绪的套接字描述符
                if (ep_events[i].data.fd == listenSocket) { //如果是监听套接字描述符就绪了,说明有一个新客户端连接到来
                
                    connSocket = accept(listenSocket); //调用accept()建立连接
                    
                    event.events = EPOLLIN;
                    event.data.fd = connSocket;
                    
                    epoll_ctl(epfd, EPOLL_CTL_ADD, connSocket, &event); //添加对新建立的连接套接字描述符的监听,以监听后续在连接描述符上的读写事件
                    
                } else { //如果是连接套接字描述符事件就绪,则可以进行读写
                
                    strlen = read(ep_events[i].data.fd, buf, BUF_SIZE); //从连接套接字描述符中读取数据, 此时一定会读到数据,不会产生阻塞
                    if (strlen == 0) { //已经无法从连接套接字中读到数据,需要移除对该socket的监听
                    
                        epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); //删除对这个描述符的监听
                        
                        close(ep_events[i].data.fd);
                    } else {
                        write(ep_events[i].data.fd, buf, str_len); //如果该客户端可写 把数据写回到客户端
                    }
                }
            }
        }
        close(listenSocket);
        close(epfd);
        return 0;
    }

    我们看这个通过epoll实现一个IO多路复用服务器的代码结构,除了由一个函数拆分成三个函数,其余的执行流程基本同select、poll相似。只是epoll会只返回已经就绪的套接字描述符集合,而不是所有描述符的集合,IO的效率不会随着监视fd的数量的增长而下降,大大提升了效率。同时它细化并规范了对每个套接字描述符的管理(如增删改的过程)。此外,它监听的套接字描述符是没有限制的,这样,之前select、poll的遗留问题就全部解决啦。

    总结

    我们从最基本网络编程说起,开始从一个最简单的同步阻塞服务器到一个IO多路复用服务器,我们从头到尾了解到了一个服务器性能提升的思考与实现过程。而提升服务器的并发性能的方式远不止这几种,还包括协程等新的概念需要我们去对比与分析。

     

    展开全文
  • socket 网络 编程

    2020-10-03 22:52:52
    自己要找到5栋楼中的一座并进入某一间房间,这时第二座楼上有人在用汉语(tcp/udp)说话,我的ip地址(楼号)是…,我的端口号(房间号)是…,就是先告诉你你的ip地址和端口号,确定你的服务器在哪里,然后回房间...

    网络编程场景
    自己是客户端站在5栋楼前,自己要找到5栋楼中的一座并进入某一间房间,这时第二座楼上有人在用汉语(tcp/udp)说话,我的ip地址(楼号)是…,我的端口号(房间号)是…,就是先告诉你你的ip地址和端口号,确定你的服务器在哪里,然后回房间监听(等待大家的来访,来了敲门)
    在这里插入图片描述

    Socket服务器和客户端的开发步骤
    在这里插入图片描述

    服务器开发
    第一步
    socket()获得一个套接字,返回套接字的描述符,后面都是基于这个描述符向外面进行网络访问,这个作用有点像open()
    第二步
    bind()为套接字添加信息,将ip地址和端口号绑定到套接字。
    第三步
    listen()监听网络连接
    第四步
    accept()监听到有客户端接入的时候,接受一个连接。
    第五步
    数据交互(read从网络通道上读数据,write向网络通道发数据)
    第六步
    关闭套接字,断开连接。
    客户端开发
    socket()给客户端一个通道,知道IP地址和端口号调用connect()连接,然后客户端write服务端read,服务端write客户端read。

    linux提供的API简析
    1、创建套接字时指定用哪种协议进行连接
    在这里插入图片描述2、地址准备好
    在这里插入图片描述

    地址转换API
    在这里插入图片描述
    4、监听
    在这里插入图片描述4、连接
    在这里插入图片描述
    数据收发
    在这里插入图片描述
    数据的收发第二套API

    在这里插入图片描述客户端connect函数
    在这里插入图片描述字节序转换API

    在这里插入图片描述
    查找结构体方法

    在这里插入图片描述在这里插入图片描述-nir中 n表示显示行号,i表示不区分大小写,r表示逐行扫描

    socket服务端代码演示

    #include<stdio.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    //#include<linux/in.h>
    #include <arpa/inet.h>
    #include<stdlib.h>
    #include<netinet/in.h>
    
    int main()
    {
            int s_fd;
            int n_read;
            char readbuf[128];
            char*msg="I have get your messange";
            //1.socket
            s_fd=socket(AF_INET,SOCK_STREAM,0);//AF_INET表示IPV4因特网域,SOCK_STREAM表示TCP协议
            if(s_fd==-1){
                    perror("socket");//打印错误
                    exit(-1);
            }
    
            struct sockaddr_in s_addr;
            struct sockaddr_in c_addr;
    
            memset(&s_addr,0,sizeof(struct sockaddr_in));
            memset(&c_addr,0,sizeof(struct sockaddr_in));
    
            s_addr.sin_family=AF_INET;//IPV4因特网域
            s_addr.sin_port=htons(8988);//htons将字节序转换为网络字节序
            inet_aton("192.168.183.128",&s_addr.sin_addr);//sin_addr是ip地址
            //2.bind
    		bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in));
    
            //3.listen
            listen(s_fd,10);
            int clen=sizeof(struct sockaddr_in);
            //4.accept
            int c_fd=accept(s_fd,(struct sockaddr *)&c_addr,&clen);//这里的c_fd就是接下来的操作对象,s_fd继续等待其他客户端的接入,accept的第二个参数是接入服务器者的信息
            if(c_fd==-1){
                    perror("accept");
            }
            printf("get connect :%s\n",inet_ntoa(c_addr.sin_addr));//打印接入客户端的IP地址
            //5.read
    
            n_read=read(c_fd,readbuf,128);
            if(n_read ==-1){
                    perror("read");
            }
            else{
                    printf("get message :%d,%s\n",n_read,readbuf);
            }
            //6.write
    
            write(c_fd,msg,strlen(msg));
            return 0;
    }
    
    

    socket客户端代码演示

    #include<stdio.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    //#include<linux/in.h>
    #include <arpa/inet.h>
    #include<stdlib.h>
    #include<netinet/in.h>
    
    int main()
    {
            int c_fd;
            int n_read;
            char* readbuf;
            readbuf=(char *)malloc(128);
            char*msg="message from client";
            //1.socket
            c_fd=socket(AF_INET,SOCK_STREAM,0);
            if(c_fd==-1){
                    perror("socket");
                    exit(-1);
            }
    
            struct sockaddr_in c_addr;
    
            memset(&c_addr,0,sizeof(struct sockaddr_in));
    
            c_addr.sin_family=AF_INET;
            c_addr.sin_port=htons(8988);
            inet_aton("192.168.183.128",&c_addr.sin_addr);
            //2.connect
            if(connect(c_fd,(struct sockaddr*)&c_addr,sizeof(struct sockaddr_in))==-1){
                    perror("connect");
                    exit(-1);
            }
            //3.send
    
            write(c_fd,msg,strlen(msg));
    
            //4.read
            n_read=read(c_fd,readbuf,128);
            if(n_read ==-1){
                    perror("read");
            }
            else{
                    printf("get message from server:%d,%s\n",n_read,readbuf);
            }
    
            return 0;
    }
    
    
    展开全文
  • linux 网络编程与 windows 网络编程

    千次阅读 2013-06-06 15:07:54
    最近写了一个程序,涉及到windows下运行的一个程序到运行linux下的服务器取出数据。一开始还真没有适应过来。下面说说我遇见的一些问题。 1、windows下connect不成功。 最开始也是不清楚哪里出错,程序...

    最近写了一个程序,涉及到在windows下运行的一个程序到运行在linux下的服务器取出数据。一开始还真没有适应过来。下面说说我遇见的一些问题。

    1、windows下connect不成功。

    最开始也是不清楚哪里出错,程序莫名其妙就在这个connect断下,而且会花费很长的时间在connect这一句停留很长时间才会跳到下一句。于是找错。函数GetLastError。输出的是10065.10065的意思就是主机不可达。于是我网上搜找了一番。发现问题所在,防火墙的问题。

    我记不得有好多问题是这个原因了。记得以前ISCSA也是有这个问题。

    关闭防火墙,service iptables stop.然后编译,connect成功,而且速度与上次是不同一个级别的。


    2、send数据到服务器成功,但是服务器发送数据过来却出错。同样的找错误,getLastError。发现是10054.网上浏览一番,发现是服务器关闭了服务。服务器代码部分是这样的:

    void worker(**)
    {
    //do what we do 
    recv(***);
    send(***); 
    close(connfd);//connfd 是链接句柄
    return NULL;
    }
    我觉得已经发出去了,然后关闭的connfd,在linux下面写一个客户端,这样是没有这样的问题的,可以很好的接受到数据。但是在windows下不管进行多少次实验,就是10054那个错误。最后为了看到一次效果,就把close(connfd)注释掉,这样就可以正常运行了,这样肯定是不成的,connfd必须关闭。



    展开全文
  • 利用网络通信中,经常会出现粘包的问题,围绕着这个问题说原因和解决的蛮多帖子的,但是给出粘包代码的就好少,为了便于大家更好的理解粘包的问题,这里对客户端和服务器端出现的粘包问题进行模拟,以方便更好的理解...
  • TCP/UDP背景知识源IP地址和目的IP地址端口号源端口号和目的端口号网络字节序socket编程socket 常见APIsockaddr结构地址转换函数UDP基本概念UDP服务器和客户端框架服务器(Server)客户端(Client)TCP基本概念三级...
  • Java网络编程——Cookie

    2020-07-01 12:45:19
    连接服务器时,服务器给客户端们每人颁发的一个通行证,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。 Cookie中可以存放用户的信息状态,以及访问的次数...
  • C++网络编程第三部分

    2009-09-25 16:52:32
    Wi n s o c k是网络编程接口,而不是协议。它从U n i x平台的B e r k e l e y(B S D)套接字方案借鉴了 许多东西,后者能访问多种网络协议。Wi n 3 2环境中,Wi n s o c k接口最终成为一个真正的 “与协议无关”...
  • Java网络编程技术2

    2015-02-02 12:23:00
    3. UDP数据报通信 UDP通信中,需要建立一个DatagramSocket,与Socket不同,它不存在“连接”的概念,取而代之的是一个数据报包——...用UDP协议通信不需要使用服务器,所以用于聊天的程序只要写一个,分别不...
  • socket库 借助于网络化,即便运行在不同的机器上,计算机程序可以彼此通信一。...为保证这一模式顺利运作,客户端必须知道要连接的服务器在哪里,也就是服务器的IP (Internet协议)地址与端口号。此外,
  • 1、TCP头部信息出现每个TCP报文段中,用于指定通信的源端端口、目的端端口、管理TCP连接等。TCP头部结构包括固定头部结构和头部选项。 2、TCP固定头部结构如图所示: (1)16位源端口号/目的号:说明这个报文...
  • 在网络编程中,实现客户端和服务器端的读写操作的时候一定要注意规范,就是读写的规定要统一!还要注意的是write()方法、print()和println()这三个方法的细小区别,的前提是读取使用的是bf.readLine()。 服务器代码...
  • 《UNIX网络编程》1990年版的前言里就指出:“想知道如何为网络开发软件 ,必须先理解进程间通信(IPC)。”  与第1版的区别  本书完全重写并扩充了1990年版《UNIX网络编程》的第3章和第18章。字数统计表明...
  • //这里的网络流关闭写在哪里啊??? 大家帮帮忙吧??? ‘ stream.Close() End Sub 现在问题是,只要客户端发送数据,服务器客户端可以接收数据,就是发送中间会出现没有反应??? 发现断开...
  • 《UNIX网络编程》1990年版的前言里就指出:“想知道如何为网络开发软件,必须先理解进程间通信(IPC)。”  与第1版的区别  本书完全重写并扩充了1990年版《UNIX网络编程》的第3章和第18章。字数统计表明,...
  • UFPB网络编程与套接字的最终活动 查看PDF说明项目 1.2组 阿里森 o 路易斯·汉里克(Luiz Henrique) 规格 它将在哪里执行? 控制面板将在运行Server类的计算机上。 客户如何知道您的位置? IP必须预先组合。 ...
  • 前面:本篇主要介绍本地套接字的作用,以及本地套接字跟网路套接字实现方式上哪里不一样,以及用本地套接字来实现服务器和客户端。   正文: 1、本地套接字的作用:本地套接字的CS模型使用来实现进程间...
  • Connect()时出错了, 我猜肯定是服务器程序有问题, 但是问题在哪里? 1 #include "unp.h" 2 3 int 4 main(int argc, char **argv) 5 { 6 int listenfd, connfd; 7 pid_t childpid; 8 socklen_t ...
  • XML高级编程

    2015-01-14 10:02:15
    14.4 在服务器上生成WML 646 14.5 WROX的WML应用 648 14.6 WML脚本 651 14.6.1 基本概念 652 14.6.2 字节码 652 14.6.3 如何从WML上调用脚本函数 652 14.7 脚本库 653 14.7.1 决定浏览器的状态—WMLBrowser 脚本库 ...
  • 从简单I/O到异步非阻塞channel的Java Socket模型演变之旅 上世纪九十年代后期,我在一家在线视频游戏工资工作,在哪里我主要的工作就是编写Unix Unix Berkley Socket和Windows WinSock代码。我的任务是确保视频游戏...
  • XML高级编程pdf

    2010-03-08 22:28:50
    14.4 在服务器上生成WML 14.5 WROX的WML应用 14.6 WML脚本 14.6.1 基本概念 14.6.2 字节码 14.6.3 如何从WML上调用脚本函数 14.7 脚本库 14.7.1 决定浏览器的状态—WMLBrowser 脚本库 14.7.2 Dialogs库 ...
  • 高级Shell脚本编程

    2013-10-28 10:08:19
    35.4. 在哪里可以获得帮助 35.5. 用来制作这本书的工具 35.5.1. 硬件 35.5.2. 软件与排版软件 35.6. 致谢 35.7. 译者致谢 参考文献 A. 捐献的脚本 B. 参考卡片 C. 一个学习Sed和Awk的小手册 C.1. Sed C.2...
  • 14.4 在服务器上生成WML 14.5 WROX的WML应用 14.6 WML脚本 14.6.1 基本概念 14.6.2 字节码 14.6.3 如何从WML上调用脚本函数 14.7 脚本库 14.7.1 决定浏览器的状态—WMLBrowser 脚本库 14.7.2 Dialogs库 ...
  • 14.4 在服务器上生成WML 14.5 WROX的WML应用 14.6 WML脚本 14.6.1 基本概念 14.6.2 字节码 14.6.3 如何从WML上调用脚本函数 14.7 脚本库 14.7.1 决定浏览器的状态—WMLBrowser 脚本库 14.7.2 Dialogs库 ...
  • 最近在学python网络编程的部分,想要网上找一些有关于UDP的例子,但是发现 都是有分服务端与客户端的,难道没有不区分的吗? 还有我这里自己写了一个小程序,要与SocketTestDlg.exe这个程序通讯,但是...
  • 大家都可以去注册一个自己的账号,然后就可以使用它的服务器就是,只要你的设备能够联网,无论你人在哪里,就能从网络上控制你的设备,而它就提供各种API接口,我们只要熟悉一点tcp的编程我们就可以简单使用了,另外...
  • 之前不求甚解,只知道SSH可以进行远程连接,不清楚Telnet,其实学过FTP,看到SFTP,也没去看他们到底区别在哪里,Shell编程学了一点也不系统,感觉好多要学。今天先来学习Telnet和SSH协议。 Telnet 简介 Telnet...

空空如也

空空如也

1 2 3 4 5
收藏数 81
精华内容 32
关键字:

在服务器网络编程在哪里