C语言实现 select模式编程
程序员文章站
2023-10-06 23:54:58
server:
// tcpserver.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include
#incl...
server:
// tcpserver.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include #include #pragma comment(lib,"ws2_32.lib") #define port 9990 #define data_bufsize 8192 // 定义套接字信息 typedef struct _socket_information { char buffer[data_bufsize]; // 发送和接收数据的缓冲区 wsabuf databuf; // 定义发送和接收数据缓冲区的结构体,包括缓冲区的长度和内容 socket socket; // 与客户端进行通信的套接字 dword bytessend; // 保存套接字发送的字节数 dword bytesrecv; // 保存套接字接收的字节数 } socket_information, * lpsocket_information; dword totalsockets = 0; // 记录正在使用的套接字总数量 lpsocket_information socketarray[fd_setsize]; // 保存socket信息对象的数组,fd_setsize表示select模型中允许的最大套接字数量 // 创建socket信息 bool createsocketinformation(socket s) { lpsocket_information si; // 用于保存套接字的信息 // printf("accepted socket number %d\n", s); // 打开已接受的套接字编号 // 为si分配内存空间 if ((si = (lpsocket_information) globalalloc(gptr, sizeof(socket_information))) == null) { printf("globalalloc() failed with error %d\n", getlasterror()); return false; } // 初始化si的值 si->socket = s; si->bytessend = 0; si->bytesrecv = 0; // 在socketarray数组中增加一个新元素,用于保存si对象 socketarray[totalsockets] = si; totalsockets++; // 增加套接字数量 return(true); } // 从数组socketarray中删除指定的lpsocket_information对象 void freesocketinformation(dword index) { lpsocket_information si = socketarray[index]; // 获取指定索引对应的lpsocket_information对象 dword i; // 关闭套接字 closesocket(si->socket); //printf("closing socket number %d\n", si->socket); // 释放指定lpsocket_information对象资源 globalfree(si); // 将数组中index索引后面的元素前移 for (i = index; i < totalsockets; i++) { socketarray[i] = socketarray[i+1]; } totalsockets--; // 套接字总数减1 } // 主函数,启动服务器 int _tmain(int argc, _tchar* argv[]) { socket listensocket; // 监听套接字 socket acceptsocket; // 与客户端进行通信的套接字 sockaddr_in internetaddr; // 服务器的地址 wsadata wsadata; // 用于初始化套接字环境 int ret; // winsock api的返回值 fd_set writeset; // 获取可写性的套接字集合 fd_set readset; // 获取可读性的套接字集合 dword total = 0; // 处于就绪状态的套接字数量 dword sendbytes; // 发送的字节数 dword recvbytes; // 接收的字节数 // 初始化winsock环境 if ((ret = wsastartup(0x0202,&wsadata)) != 0) { printf("wsastartup() failed with error %d\n", ret); wsacleanup(); return -1; } // 创建用于监听的套接字 if ((listensocket = wsasocket(af_inet, sock_stream, 0, null, 0, wsa_flag_overlapped)) == invalid_socket) { printf("wsasocket() failed with error %d\n", wsagetlasterror()); return -1; } // 设置监听地址和端口号 internetaddr.sin_family = af_inet; internetaddr.sin_addr.s_addr = htonl(inaddr_any); internetaddr.sin_port = htons(port); // 绑定监听套接字到本地地址和端口 if(bind(listensocket, (psockaddr)&internetaddr, sizeof(internetaddr)) == socket_error) { printf("bind() failed with error %d\n", wsagetlasterror()); return -1; } // 开始监听 if (listen(listensocket, 5)) { printf("listen() failed with error %d\n", wsagetlasterror()); return -1; } /* // 设置为非阻塞模式 ulong nonblock = 1; if(ioctlsocket(listensocket, fionbio, &nonblock) == socket_error) { printf("ioctlsocket() failed with error %d\n", wsagetlasterror()); return -1; } // 为listensocket套接字创建对应的socket_information // 这样就可以把listensocket添加到socketarray数组中 */ createsocketinformation(listensocket); while(true) { // 准备用于网络i/o通知的读/写套接字集合 fd_zero(&readset); fd_zero(&writeset); // 向readset集合中添加监听套接字listensocket fd_set(listensocket, &readset); // 将socketarray数组中的所有套接字添加到writeset和readset集合中 // socketarray数组中保存着监听套接字和所有与客户端进行通信的套接字 // 这样就可以使用select()判断哪个套接字有接入数据或者读取/写入数据 for (dword i=0; isocket, &writeset); fd_set(socketinfo->socket, &readset); } // 判断读/写套接字集合中就绪的套接字 if((total = select(0, &readset, &writeset, null, null)) == socket_error) { printf("select() returned with error %d\n", wsagetlasterror()); return -1; } // 依次处理所有套接字。本服务器是一个回应服务器,即将从客户端收到的字符串再发回到客户端。 for (dword i=0; isocket, &readset)) { if(socketinfo->socket == listensocket) // 对于监听套接字来说,可读表示有新的连接请求 { total--; // 就绪的套接字减1 // 接受连接请求,得到与客户端进行通信的套接字acceptsocket if((acceptsocket = accept(listensocket, null, null)) != invalid_socket) { printf("连接成功!\n"); /* // 设置套接字acceptsocket为非阻塞模式 // 这样服务器在调用wsasend()函数发送数据时就不会被阻塞 nonblock = 1; if(ioctlsocket(acceptsocket, fionbio, &nonblock) == socket_error) { printf("ioctlsocket() failed with error %d\n", wsagetlasterror()); return -1; } */ // 创建套接字信息,初始化lpsocket_information结构体数据,将acceptsocket添加到socketarray数组中 if(createsocketinformation(acceptsocket) == false) return -1; } else { if(wsagetlasterror() != wsaewouldblock) { printf("accept() failed with error %d\n", wsagetlasterror()); return -1; } } } else // 接收数据 { // 如果当前套接字在readset集合中,则表明该套接字上有可以读取的数据 if (fd_isset(socketinfo->socket, &readset)) { total--; // 减少一个处于就绪状态的套接字 memset(socketinfo->buffer, ' ', data_bufsize); zeromemory(socketinfo->databuf.buf,data_bufsize);// 初始化缓冲区 socketinfo->databuf.buf = socketinfo->buffer; // 初始化缓冲区位置 socketinfo->databuf.len = data_bufsize; // 初始化缓冲区长度 // 接收数据 dword flags = 0; if(wsarecv(socketinfo->socket, &(socketinfo->databuf), 1, &recvbytes, &flags, null, null) == socket_error) { // 错误编码等于wsaewouldblock表示暂没有数据,否则表示出现异常 if(wsagetlasterror() != wsaewouldblock) { printf("wsarecv() failed with error %d\n", wsagetlasterror()); freesocketinformation(i); // 释放套接字信息 } continue; } else // 接收数据 { socketinfo->bytesrecv = recvbytes; // 记录接收数据的字节数 if(recvbytes == 0) // 如果接收到0个字节,则表示对方关闭连接 { freesocketinformation(i); continue; } else // 如果成功接收数据,则打印收到的数据 { printf(socketinfo->databuf.buf); printf("\n"); // } } } } } else { // 如果当前套接字在writeset集合中,则表明该套接字的内部数据缓冲区中有数据可以发送 if(fd_isset(socketinfo->socket, &writeset)) { total--; // 减少一个处于就绪状态的套接字 socketinfo->databuf.buf = socketinfo->buffer + socketinfo->bytessend; // 初始化缓冲区位置 socketinfo->databuf.len = socketinfo->bytesrecv - socketinfo->bytessend; // 初始化缓冲区长度 if(socketinfo->databuf.len > 0) // 如果有需要发送的数据,则发送数据 { if(wsasend(socketinfo->socket, &(socketinfo->databuf), 1, &sendbytes, 0, null, null) == socket_error) { // 错误编码等于wsaewouldblock表示暂没有数据,否则表示出现异常 if(wsagetlasterror() != wsaewouldblock) { printf("wsasend() failed with error %d\n", wsagetlasterror()); freesocketinformation(i); // 释放套接字信息 } continue; } else { socketinfo->bytessend += sendbytes; // 记录发送数据的字节数 // 如果从客户端接收到的数据都已经发回到客户端,则将发送和接收的字节数量设置为0 if (socketinfo->bytessend == socketinfo->bytesrecv) { socketinfo->bytessend = 0; socketinfo->bytesrecv = 0; } } } } } // 如果listensocket未就绪,并且返回的错误不是wsaewouldblock(该错误表示没有接收的连接请求),则出现异常 } } // 暂停,按任意键退出 system("pause"); return 0; }
client:
// tcpclient.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include #include #include #pragma comment(lib,"ws2_32.lib") #define buf_size 64 // 缓冲区大小 int _tmain(int argc, _tchar* argv[]) { wsadata wsd; // 用于初始化windows socket socket shost; // 与服务器进行通信的套接字 sockaddr_in servaddr; // 服务器地址 char buf[buf_size]; // 用于接受数据缓冲区 int retval; // 调用各种socket函数的返回值 // 初始化windows socket if(wsastartup(makeword(2,2),&wsd) != 0) { printf("wsastartup failed !\n"); return 1; } // 创建套接字 shost = socket(af_inet,sock_stream,ipproto_ip); if(invalid_socket == shost) { printf("socket failed !\n"); wsacleanup(); return -1; } // 设置服务器地址 servaddr.sin_family = af_inet; servaddr.sin_addr.s_un.s_addr = inet_addr("127.0.0.1"); // 用户需要根据实际情况修改 servaddr.sin_port = htons(9990); // 在实际应用中,建议将服务器的ip地址和端口号保存在配置文件中 int sserveraddlen = sizeof(servaddr); // 计算地址的长度 // 循环等待 while(true) { // 连接服务器 sleep( 200 ); retval = connect(shost,(lpsockaddr)&servaddr,sizeof(servaddr)); sleep( 200 ); if(socket_error == retval) { int err = wsagetlasterror(); if(err == wsaewouldblock || err == wsaeinval) // 无法立即完成非阻塞套接字上的操作 { //sleep(500); continue; } else if(err == wsaeisconn) // 已建立连接 { break; } else { continue; //printf("connect failed !\n"); //closesocket(shost); //wsacleanup(); //return -1; } } } // 循环向服务器发送字符串,并显示反馈信息。 // 发送quit将使服务器程序退出,同时客户端程序自身也将退出 while(true) { // 向服务器发送数据 printf("please input a string to send: "); // 接收输入的数据 std::string str; std::getline(std::cin, str); // 将用户输入的数据复制到buf中 zeromemory(buf,buf_size); zeromemory(buf,buf_size); strcpy(buf,str.c_str()); // 循环等待 while(true) { // 向服务器发送数据 retval = send(shost,buf,strlen(buf),0); if(socket_error == retval) { int err = wsagetlasterror(); if(err == wsaewouldblock) // 无法立即完成非阻塞套接字上的操作 { continue; } else { printf("send failed !\n"); closesocket(shost); wsacleanup(); return -1; } } break; } while(true) { zeromemory(buf,buf_size); // 清空接收数据的缓冲区 retval = recv(shost,buf,sizeof(buf)+1,0); // 接收服务器回传的数据 if(socket_error == retval) { int err = wsagetlasterror(); // 获取错误编码 if(err == wsaewouldblock) // 接收数据缓冲区暂无数据 { sleep(100); printf("waiting back msg !\n"); continue; } else if(err == wsaetimedout || err == wsaenetdown) { printf("recv failed !\n"); closesocket(shost); wsacleanup(); return -1; } break; } break; } //zeromemory(buf,buf_size); // 清空接收数据的缓冲区 //retval = recv(shost,buf,sizeof(buf)+1,0); // 接收服务器回传的数据 printf("recv from server: %s\n",buf); // 如果收到quit,则退出 if(strcmp(buf, "quit") == 0) { printf("quit!\n"); break; } } // 释放资源 closesocket(shost); wsacleanup(); // 暂停,按任意键继续 system("pause"); return 0; }