tcp/ip编程之多客户端的实现

来源:互联网 发布:淘宝助理快速发布宝贝 编辑:程序博客网 时间:2024/06/05 14:03

第三章 多客户端的实现

第一章 基础知识中给出的模板,udp可以实现多客户端,但是tcp只实现了一对一的对话,要实现多个客户端的连接,主要有以下几种方式。

一、利用多线程

客户端代码不用变,服务端代码在之前的代码中加以改进。只需要将每一个连接放到一个线程中处理,就可以实现多用户连接。(以下代码只显示发送内容,对客户端的身份未进行标识,可自行添加)
#include<winsock2.h>#include <stdio.h>#include <stdlib.h>#pragma comment(lib, "ws2_32.lib") #define DEFAULT_PORT 5051class Server{int iPort;WSADATA wsaData;SOCKET sListen, sAccept;char sendbuf[100];int iLen, iSend;struct sockaddr_in ser, cli;public:int init();void getdata();static DWORD WINAPI clientService(LPVOID lpParameter);};int Server::init(){iPort = DEFAULT_PORT;strcpy(sendbuf,"hellow i am a sever");if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){printf("Failed to load Winsock.\n");return 0;}sListen = socket(AF_INET, SOCK_STREAM, 0);if (sListen == INVALID_SOCKET){printf("SOCKET() Failed:%d\n", WSAGetLastError());return 0;}ser.sin_family = AF_INET;ser.sin_port = htons(iPort);ser.sin_addr.S_un.S_addr =htonl(INADDR_ANY); //inet_addr("192.168.1.109") if (bind(sListen, (LPSOCKADDR)&ser, sizeof(ser)) == SOCKET_ERROR){printf("bind() failed:%d\n", WSAGetLastError());return 0;}if (listen(sListen, 5) == SOCKET_ERROR){printf("listen() failed:%d\n", WSAGetLastError);return 0;}iLen = sizeof(cli);return 1;}void Server::getdata(){HANDLE hThread=NULL; while (1){sAccept = accept(sListen, (struct sockaddr*)&cli, &iLen);printf("accept");if (sAccept == INVALID_SOCKET){printf("accept failed:%d\n", WSAGetLastError());break;}printf("Accept client IP:[%s],port:[%d] \n", inet_ntoa(cli.sin_addr), ntohs(cli.sin_port));iSend = send(sAccept, sendbuf, sizeof(sendbuf), 0);if (iSend == SOCKET_ERROR){printf("send() failed:%d\n", WSAGetLastError());break;}hThread=CreateThread(NULL,0,clientService,(LPVOID)sAccept, 0, NULL);          if (hThread==NULL)          {              printf("Create Thread Failed!\n");          }          CloseHandle(hThread);  }closesocket(sAccept);}DWORD WINAPI Server::clientService(LPVOID lpParameter)  {      SOCKET clientSocket=(SOCKET)lpParameter; char recvbuf[100];while(1){memset(recvbuf,0,sizeof(recvbuf));  int err=recv(clientSocket,recvbuf,sizeof(recvbuf), 0);if (err==0||err==SOCKET_ERROR)          {              printf("客户端退出\n");              break;          }  if(strcmp(recvbuf,"end")!=0)printf("%s\n",recvbuf);//inet_ntoa(cli.sin_addr)}return 0;}void main(){Server ser;if(ser.init()==0){printf(".............");printf("Sever init error\n");}printf(".............");printf("Sever waitting\n");ser.getdata();}

二、利用非阻塞

        套接字相关函数默认时采用阻塞方式操作,如果希望这些套接字同时工作,就必须设计并发的套接字程序,即在一个套接字读写的同时保证另一个套接字也能正常地操作。多路复用函数 select 把一些文件描述符集合在一起,如果某个文件描述符的状态发生变化,比如进入“写就绪”或者“读就绪”状态,函数 select 会立即返回,并且通知进程读取或写入数据;如果没有 I/O 到达,进程将进入阻塞,直到函数 select 超时退出为止。第二章针对api函数有介绍可查看。
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
select的大概思想:将多个套接字放在一个集合里,然后统一检查这些套接字的状态(可读、可写、异常等),调用select后,会更新这些套接字的状态,然后做判断,如果套接字可读,就执行read操作。这样就巧妙地避免了阻塞,达到同时处理多个连接的目的。当然如果没有事件发生,select会一直阻塞,如果不想一直让它等待,想去处理其它事情,可以设置一个最大的等待时间。
函数返回值:  
负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件 
对fd_set结构体的操作:
  1. FD_ZERO(fd_set * set),是把集合清空(初始化为0,确切的说,是把集合中的元素个数初始化为0,并不修改描述符数组).使用集合前,必须用FD_ZERO初始化,否则集合在栈上作为自动变量分配时,fd_set分配的将是随机值,导致不可预测的问题。
  1. FD_SET(int s,fd_set * set),向集合中加入一个套接口描述符(如果该套接口描述符s没在集合中,并且数组中已经设置的个数小于最大个数时,就把该描述符加入到集合中,集合元素个数加1)。这里是将s的值直接放入数组中。
  1. FD_ISSET(int s,fd_set * set),检查描述符是否在集合中,如果在集合中返回非0值,否则返回0. 它的宏定义并没有给出具体实现,但实现的思路很简单,就是搜索集合,判断套接字s是否在数组中。
  1. FD_CLR(int s,fd_set * set),从集合中移出一个套接口描述符(比如一个套接字连接中断后,就应该移除它)。实现思路是,在数组集合中找到对应的描述符,然后把后面的描述依次前移一个位置,最后把描述符的个数减1。
  2. 客户端不变
  3. 服务端:
#include<winsock2.h>#include <stdio.h>#include <stdlib.h>#pragma comment(lib, "ws2_32.lib") #define DEFAULT_PORT 5051#include<iostream>using namespace std;DWORD WINAPI workThread(LPVOID lpParam) ;class Server{int iPort;WSADATA wsaData;SOCKET sListen, sAccept;char sendbuf[100];int iLen, iSend;struct sockaddr_in ser, cli;public:int init();void getdata();};class SocketList  {      private:      int num;//记录socket的真实数目      SOCKET socketArray[FD_SETSIZE];//存放socket的数组        public:      SOCKET getSocket(int i)      {          return socketArray[i];      }        //构造函数中对两个成员变量进行初始化      SocketList()      {          num=0;          for (int i=0;i<FD_SETSIZE;i++)          {              //因为socket的值是一个非负整数值,所以可以将socketArray初始化为0,让它来表示数组中的这一个元素有没有被使用              socketArray[i]=0;          }      }        //往socketArray中添加一个socket      void insertSocket(SOCKET s)      {          for (int i=0;i<FD_SETSIZE;i++)          {              //如果某一个socketArray[i]为0,表示哪一个位可以放入socket              if (socketArray[i]==0)              {                  socketArray[i]=s;                  num++;                  break;//这里一定要加上break,不然一个socket会放在socketArray的多个位置上              }          }      }        //从socketArray中删除一个socket      void deleteSocket(SOCKET s)      {          for (int i=0;i<FD_SETSIZE;i++)          {              if (socketArray[i]==s)              {                  socketArray[i]=0;                  num--;                  break;              }          }      }        //将socketArray中的套接字放入fd_list这个结构体中      void makefd(fd_set * fd_list)      {              FD_ZERO(fd_list);//首先将fd_list清0                //将每一个socket加入fd_list中              for (int i=0;i<FD_SETSIZE;i++)              {                  if (socketArray[i]>0)                  {                      FD_SET(socketArray[i],fd_list);                  }              }      }    };  int Server::init(){iPort = DEFAULT_PORT;strcpy(sendbuf,"hellow i am a sever");if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0){printf("Failed to load Winsock.\n");return 0;}sListen = socket(AF_INET, SOCK_STREAM, 0);if (sListen == INVALID_SOCKET){printf("SOCKET() Failed:%d\n", WSAGetLastError());return 0;}ser.sin_family = AF_INET;ser.sin_port = htons(iPort);ser.sin_addr.S_un.S_addr =htonl(INADDR_ANY); //inet_addr("192.168.1.109") if (bind(sListen, (LPSOCKADDR)&ser, sizeof(ser)) == SOCKET_ERROR){printf("bind() failed:%d\n", WSAGetLastError());return 0;}if (listen(sListen,10 )== SOCKET_ERROR){printf("listen() failed:%d\n", WSAGetLastError);return 0;}iLen = sizeof(cli);return 1;}void Server::getdata(){SocketList socketList;      HANDLE hThread=CreateThread(NULL,0,workThread,&socketList,0,NULL);      if (hThread==NULL)      {          cout<<"Create Thread Failed!"<<endl;      }      CloseHandle(hThread);  while (1){sAccept = accept(sListen, (struct sockaddr*)&cli, &iLen);printf("accept");if (sAccept == INVALID_SOCKET){printf("accept failed:%d\n", WSAGetLastError());break;}printf("Accept client IP:[%s],port:[%d] \n", inet_ntoa(cli.sin_addr), ntohs(cli.sin_port));iSend = send(sAccept, sendbuf, sizeof(sendbuf), 0);if (iSend == SOCKET_ERROR){printf("send() failed:%d\n", WSAGetLastError());break;}socketList.insertSocket(sAccept);  }closesocket(sAccept);}DWORD WINAPI workThread(LPVOID lpParam)  {          //传递进来的socketList指针          SocketList * socketList=(SocketList *)lpParam;          int err=0;          fd_set fdread;//存在读文件的set,select会检测这个set中是否可以从某些socket中读入信息            struct timeval timeout;//设置select超时的时间          timeout.tv_sec=6;          timeout.tv_usec=0;            //输入输出缓冲区          char receBuff[MAX_PATH];            SOCKET socket;            while(true)          {                  socketList->makefd(&fdread);                  err=select(0,&fdread,NULL,NULL,&timeout);                  if (err==0)//select返回0表示超时                  {                      cout<<"select() is time-out!"<<endl;                      continue;                  }                  else                  {                      //遍历socketList中的每一个socket,查看那些socket是可读的,处理可读的socket                      //从中读取数据到缓冲区,并发送数据给客户端                          for (int i=0;i<FD_SETSIZE;i++)                          {                              //读取有效的socket                              if (socketList->getSocket(i)==0)                                      continue;                              socket=socketList->getSocket(i);                                //判断哪些socket是可读的,如果这个socket是可读的,从它里面读取数据                              if (FD_ISSET(socket,&fdread))                              {                                  err=recv(socket,receBuff,MAX_PATH,0);                                    //如果返回值表示要关闭这个连接,那么关闭它,并将它从sockeList中去掉                                  if (err==0||err==SOCKET_ERROR)                                  {                                      closesocket(socket);                                      cout<<"断开连接!"<<endl;                                      socketList->deleteSocket(socket);                                  }                                  else                                  {                                      cout<<receBuff<<endl;                                      }                                }//end if                            }//end for                    }//end if (err==0)            }//end while              return 0;  }  void main(){Server ser;if(ser.init()==0){printf(".............");printf("Sever init error\n");}printf(".............");printf("Sever waitting\n");ser.getdata();}

三、重叠IO模型

见:http://m.blog.csdn.net/article/details?id=48182783

阅读全文
0 0