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结构体的操作:
- FD_ZERO(fd_set * set),是把集合清空(初始化为0,确切的说,是把集合中的元素个数初始化为0,并不修改描述符数组).使用集合前,必须用FD_ZERO初始化,否则集合在栈上作为自动变量分配时,fd_set分配的将是随机值,导致不可预测的问题。
- FD_SET(int s,fd_set * set),向集合中加入一个套接口描述符(如果该套接口描述符s没在集合中,并且数组中已经设置的个数小于最大个数时,就把该描述符加入到集合中,集合元素个数加1)。这里是将s的值直接放入数组中。
- FD_ISSET(int s,fd_set * set),检查描述符是否在集合中,如果在集合中返回非0值,否则返回0. 它的宏定义并没有给出具体实现,但实现的思路很简单,就是搜索集合,判断套接字s是否在数组中。
- FD_CLR(int s,fd_set * set),从集合中移出一个套接口描述符(比如一个套接字连接中断后,就应该移除它)。实现思路是,在数组集合中找到对应的描述符,然后把后面的描述依次前移一个位置,最后把描述符的个数减1。
- 客户端不变
- 服务端:
#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
- tcp/ip编程之多客户端的实现
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- WINDOWS (服务器) 和 DOS(客户端) 网络互连 基于TCP/IP的编程实现
- TCP/IP编程实现
- VC++ Socket编程 简单的Tcp/ip客户端
- MFC网络编程TCP/IP的服务器与客户端代码
- TCP IP网络编程1 简单的回声服务器端、客户端
- 基于TCP/IP的套接字服务器端和客户端编程
- socket编程—TCP/IP 多客户端
- 通信之多客户端传输文件的实现
- VC++中Socket编程的实现---【TCP客户端】
- VC++中Socket编程的实现-TCP客户端
- Java socket网络编程TCP客户端的实现
- 实现dos real model下的TCP/IP编程(上) 关键字dos, socket, tcp ip, wattcp
- tcp/ip 多线程服务器端的实现(参考tcp/ip网络编程)
- 使用的bean工厂ApplicationContext
- No207. Course Schedule
- Adventure of Super Mario UVA
- HDFS权限控制
- 【js实例】js中的5种基本数据类型和9种操作符
- tcp/ip编程之多客户端的实现
- CT及MR的定位线功能实现
- 微网页开发弹出“非微信官方网页 转换为手机预览模式”的原因
- 两个easyui combobox 逻辑关联
- android动态适配
- Kotlin 基本语法 (一)
- bzoj 3126: [Usaco2013 Open]Photo (DP+单调队列)
- Windows Socket套接字(四)-Windows套接字错误代码
- jquery利用attr、prop方法获取、设置input的checked属性