网络编程 网络基础知识 网络OSI模型 网络OSI7层模型(Open Systems Interconnection)即开放系统互联。它是由ISO(Internet Organization for Standardization)制定
OSI时网络通讯的一种通用框架,它分为七层
序号
层
7
应用层(Application)
6
表示层(Presentation)
5
会话层(Session)
4
传输层(Transport)->TCP/UDP
3
网络层(NetWork)
2
数据链路层(Data Link)
1
物理层(Physical)
但不是所有网络通信都必须经过这7层模型,比如同一网段下两台计算机的交互,只需要经过物理层和数据链路层就够了。
网络上最重要的就是IP,IP就在网络层。
Windows Socket 什么是套接字(Socket) Socket又称套接字,它是TCP/IP网络环境下应用程序与底层通信驱动程序之间运行的开发接口.它可以将应用程序与具体的TCP/IP隔离开.使得应用程序不需要了解TCP/IP的细节,就能够实现传输。
套接字的服务方式和类型 根据底层协议的不同,Socket开发接口可以提供面向连接和无连接二种服务方式。
在Socketi通信中,套接字分为3种类型。
1.流式套接字.SOCK_STREAM
流式套接字是面向连接的、提供双向、有序、无重复且无记录边界的数据流服务,适用于处理大量数据,可靠性高,但开销也大。
2.数据报式套接字SOCK_DGRAM
UDP,一般应用在传输音视频文件,容许少量丢包
3.原始套接字
其中SOCK_STREAM、SOCK_DGRAM工作在传输层,SOCK_RAW工作在网络层。
构建WinSock应用程序框架 WinSock包含了两个主要的版本,即Socket1和winsock2,我们一般使用2 在使用时,需要包含如下头文件和lib文件 #include <WinSock2.h> #pragma comment(lib,”ws2_32.lib”)
还需要初始化WinSocket Int WSAAPI WSAStartup(WORD wVersionRequested //版本号,LPWSADATA IpWSAData); WSAData结构用于存放返回的Socket数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <stdio.h> #include <WinSock2.h> #pragma comment(lib,"ws2_32.lib" ) int main () { WORD wVersion = MAKEWORD(2 , 2 ); WSADATA wsaData; if (WSAStartup(wVersion, &wsaData)) { printf ("WSAStartup failed" ); return 3 ; } else { printf ("WSAStartup successed" ); } return 2 ; }
WSAData,存放Winsock返回的初始化信息:
1 2 3 4 5 6 7 8 9 typedef struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[WSADESCRIPTION_LEN+1 ]; char szSystemStatus[WSASYSSTATUS_LEN+1 ]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char FAR * lpVendorInfo; };
IP地址的表现形式 IP地址常用点分法来表示比如192.168.0.1,即4个0-255的整数表示
但是在计算机中不使用点分法来保存IP地址.这样会浪费存诸空间.而且不便于根据IP地址和子网掩码来计算子网信息 所以在计算机中使用无符号长整形数来存诸表示IP地址 1.网络字节顺序(NetWork Byte Order,NBO) 在网络传送中,IP地址被保存为32位的二进制数在低位存储地址中保存数据的高位字节这种存储顺序格式被称为网络字节顺序.数据按照32位的二进制数为一组进行传输。因为采用网络字节顺序,所以数据的传输顺序是由高到低的。 在VC中使用In_addr来保存IP地址。 inet_addr和 inet_ntoa
in_addr:
1 2 3 4 5 6 7 8 9 10 11 12 13 typedef struct in_addr { union { struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b; struct { USHORT s_w1,s_w2; } S_un_w; ULONG S_addr; } S_un; #define s_addr S_un.S_addr #define s_host S_un.S_un_b.s_b2 #define s_net S_un.S_un_b.s_b1 #define s_imp S_un.S_un_w.s_w2 #define s_impno S_un.S_un_b.s_b4 #define s_lh S_un.S_un_b.s_b3 } IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;
2.主机字节顺序(Host Byte Order,HBO)两种格式 不同的主机在对IP地址进行存储时使用的格式也不同,所以需要通过下列四个函数 来实现主机和网络字节顺序的互转 htonl 将主机字节顺序格式IP地址转换成为TCP/IP网络字节顺序 Host to Net u_long htons主机转网络u_short型 ntohl网络转主机 u_long Net to Host u_long ntohs网络转主机 u_short
WinSock TCP/IP连接流程
服务端
客户端
1.建立流式套接字
1.建立流式套接字
2.套接字:与本地地址绑定
3.通知TCP服务器准备连接
4.等待客户端的连接
2.将套接字与远程主机连接
5.建立连接
6.在套接字上读写数据
3.在套接字上读写数据
7.关闭套接字
4.关闭套接字结束对话
8.关闭最初的套接字,结束服务
地址族:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #define AF_UNSPEC 0 #define AF_UNIX 1 #define AF_INET 2 #define AF_IMPLINK 3 #define AF_PUP 4 #define AF_CHAOS 5 #define AF_NS 6 #define AF_IPX AF_NS #define AF_ISO 7 #define AF_OSI AF_ISO #define AF_ECMA 8 #define AF_DATAKIT 9 #define AF_CCITT 10 #define AF_SNA 11 #define AF_DECnet 12 #define AF_DLI 13 #define AF_LAT 14 #define AF_HYLINK 15 #define AF_APPLETALK 16 #define AF_NETBIOS 17 #define AF_VOICEVIEW 18 #define AF_FIREFOX 19 #define AF_UNKNOWN1 20 #define AF_BAN 21 #define AF_ATM 22 #define AF_INET6 23 #define AF_CLUSTER 24 #define AF_12844 25 #define AF_IRDA 26 #define AF_NETDES 28
套接字类型:
1 2 3 4 5 #define SOCK_STREAM 1 #define SOCK_DGRAM 2 #define SOCK_RAW 3 #define SOCK_RDM 4 #define SOCK_SEQPACKET 5
套接字使用的协议:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 typedef enum {#if (_WIN32_WINNT >= 0x0501) IPPROTO_HOPOPTS = 0 , #endif IPPROTO_ICMP = 1 , IPPROTO_IGMP = 2 , IPPROTO_GGP = 3 , #if (_WIN32_WINNT >= 0x0501) IPPROTO_IPV4 = 4 , #endif #if (_WIN32_WINNT >= 0x0600) IPPROTO_ST = 5 , #endif IPPROTO_TCP = 6 , #if (_WIN32_WINNT >= 0x0600) IPPROTO_CBT = 7 , IPPROTO_EGP = 8 , IPPROTO_IGP = 9 , #endif IPPROTO_PUP = 12 , IPPROTO_UDP = 17 , IPPROTO_IDP = 22 , #if (_WIN32_WINNT >= 0x0600) IPPROTO_RDP = 27 , #endif #if (_WIN32_WINNT >= 0x0501) IPPROTO_IPV6 = 41 , IPPROTO_ROUTING = 43 , IPPROTO_FRAGMENT = 44 , IPPROTO_ESP = 50 , IPPROTO_AH = 51 , IPPROTO_ICMPV6 = 58 , IPPROTO_NONE = 59 , IPPROTO_DSTOPTS = 60 , #endif IPPROTO_ND = 77 , #if (_WIN32_WINNT >= 0x0501) IPPROTO_ICLFXBM = 78 , #endif #if (_WIN32_WINNT >= 0x0600) IPPROTO_PIM = 103 , IPPROTO_PGM = 113 , IPPROTO_L2TP = 115 , IPPROTO_SCTP = 132 , #endif IPPROTO_RAW = 255 , IPPROTO_MAX = 256 , IPPROTO_RESERVED_RAW = 257 , IPPROTO_RESERVED_IPSEC = 258 , IPPROTO_RESERVED_IPSECOFFLOAD = 259 , IPPROTO_RESERVED_WNV = 260 , IPPROTO_RESERVED_MAX = 261 } IPPROTO, *PIPROTO;
代码 服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib" ) #define PORT 8080 #define BUFSIZE 1024 int main () { WSADATA wsaData; int iResult; SOCKET server_fd, new_socket; struct sockaddr_in address ; int addrlen = sizeof (address); char buffer[BUFSIZE] = {0 }; const char *hello = "Hello from server" ; iResult = WSAStartup(MAKEWORD(2 , 2 ), &wsaData); if (iResult != 0 ) { printf ("WSAStartup failed: %d\n" , iResult); return 1 ; } if ((server_fd = socket(AF_INET, SOCK_STREAM, 0 )) == INVALID_SOCKET) { perror("socket failed" ); WSACleanup(); return 1 ; } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof (address)) == SOCKET_ERROR) { perror("bind failed" ); closesocket(server_fd); WSACleanup(); return 1 ; } if (listen(server_fd, 3 ) == SOCKET_ERROR) { perror("listen" ); closesocket(server_fd); WSACleanup(); return 1 ; } if ((new_socket = accept(server_fd, (struct sockaddr *)&address, &addrlen)) == INVALID_SOCKET) { perror("accept" ); closesocket(server_fd); WSACleanup(); return 1 ; } iResult = recv(new_socket, buffer, BUFSIZE, 0 ); if (iResult > 0 ) { printf ("Received: %s\n" , buffer); } else if (iResult == 0 ) { printf ("Connection closing...\n" ); } else { perror("recv failed" ); closesocket(new_socket); closesocket(server_fd); WSACleanup(); return 1 ; } send(new_socket, hello, strlen (hello), 0 ); printf ("Hello message sent\n" ); closesocket(new_socket); closesocket(server_fd); WSACleanup(); return 0 ; }
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib" ) #define PORT 8080 #define BUFSIZE 1024 int main () { WSADATA wsaData; int iResult; SOCKET client_fd; struct sockaddr_in serv_addr ; char buffer[BUFSIZE] = {0 }; const char *hello = "Hello from client" ; iResult = WSAStartup(MAKEWORD(2 , 2 ), &wsaData); if (iResult != 0 ) { printf ("WSAStartup failed: %d\n" , iResult); return 1 ; } if ((client_fd = socket(AF_INET, SOCK_STREAM, 0 )) == INVALID_SOCKET) { perror("socket failed" ); WSACleanup(); return 1 ; } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); if (inet_pton(AF_INET, "127.0.0.1" , &serv_addr.sin_addr) <= 0 ) { perror("inet_pton failed" ); closesocket(client_fd); WSACleanup(); return 1 ; } if (connect(client_fd, (struct sockaddr *)&serv_addr, sizeof (serv_addr)) == SOCKET_ERROR) { perror("connect failed" ); closesocket(client_fd); WSACleanup(); return 1 ; } send(client_fd, hello, strlen (hello), 0 ); printf ("Hello message sent\n" ); iResult = recv(client_fd, buffer, BUFSIZE, 0 ); if (iResult > 0 ) { printf ("Received: %s\n" , buffer); } else if (iResult == 0 ) { printf ("Connection closed\n" ); } else { perror("recv failed" ); } closesocket(client_fd); WSACleanup(); return 0 ; }
面向非连接的SOCKET编程 特点 面向非连接(UDP)的特点:
1.不需要在服务端和客户端之间进建立连接
2.不对收到的数据进行排序
3.对接收到的数据包不发送确认信息,发送端无法知道数据是否被正确接收,也不会重新发送数据
4.传送数据较TCP快,系统开销也少
UDP连接流程
主机流程(UDP)
客户端流程
1.建立流式套接字
1.建立流式套接字
2.套接字:与本地地址绑定
3.在套接字上读/写数据
2.在套接字上读/写数据
4.关闭套接字
3.关闭套接字
代码 服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib" ) #define PORT 8080 #define BUFSIZE 1024 int main () { WSADATA wsaData; int iResult; SOCKET server_fd; struct sockaddr_in address , client_addr ; int addrlen = sizeof (client_addr); char buffer[BUFSIZE] = {0 }; const char *hello = "Hello from server" ; iResult = WSAStartup(MAKEWORD(2 , 2 ), &wsaData); if (iResult != 0 ) { printf ("WSAStartup failed: %d\n" , iResult); return 1 ; } if ((server_fd = socket(AF_INET, SOCK_DGRAM, 0 )) == INVALID_SOCKET) { perror("socket failed" ); WSACleanup(); return 1 ; } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof (address)) == SOCKET_ERROR) { perror("bind failed" ); closesocket(server_fd); WSACleanup(); return 1 ; } iResult = recvfrom(server_fd, buffer, BUFSIZE, 0 , (struct sockaddr *)&client_addr, &addrlen); if (iResult > 0 ) { printf ("Received: %s\n" , buffer); } else { perror("recvfrom failed" ); closesocket(server_fd); WSACleanup(); return 1 ; } sendto(server_fd, hello, strlen (hello), 0 , (struct sockaddr *)&client_addr, addrlen); printf ("Hello message sent\n" ); closesocket(server_fd); WSACleanup(); return 0 ; }
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib" ) #define PORT 8080 #define BUFSIZE 1024 int main () { WSADATA wsaData; int iResult; SOCKET client_fd; struct sockaddr_in serv_addr ; char buffer[BUFSIZE] = {0 }; const char *hello = "Hello from client" ; iResult = WSAStartup(MAKEWORD(2 , 2 ), &wsaData); if (iResult != 0 ) { printf ("WSAStartup failed: %d\n" , iResult); return 1 ; } if ((client_fd = socket(AF_INET, SOCK_DGRAM, 0 )) == INVALID_SOCKET) { perror("socket failed" ); WSACleanup(); return 1 ; } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); if (inet_pton(AF_INET, "127.0.0.1" , &serv_addr.sin_addr) <= 0 ) { perror("inet_pton failed" ); closesocket(client_fd); WSACleanup(); return 1 ; } sendto(client_fd, hello, strlen (hello), 0 , (struct sockaddr *)&serv_addr, sizeof (serv_addr)); printf ("Hello message sent\n" ); iResult = recvfrom(client_fd, buffer, BUFSIZE, 0 , NULL , NULL ); if (iResult > 0 ) { printf ("Received: %s\n" , buffer); } else { perror("recvfrom failed" ); } closesocket(client_fd); WSACleanup(); return 0 ; }
阻塞式模型 阻塞式 什么是阻塞式?
1.在创建一个套接字后,默认都是阻塞式的Winsocket的IO函数比如:Send和Recv,必须等待函数完成相应的I/O操作后,才能继续
什么是非阻塞式?
1.通过调用ioctlsocket(SOCKET s, long cmd, u_long *arpg)函数,改变该套接字的模式, U_long nNoBlock =1; loctlsocket(s,FIONBIO,&nNoBlock);
2.无论操作是否完成,非阻塞式函数都会立即返回。例如,在非阻塞模式下调用recv接收数据时,程序会直接读取网络缓冲区中的数据。无论是否读到数据,函数都会立即返回
阻塞式迭代模式 1.每次只服务一个连接,只有在服务完当前客户端连接之后,才会继续服务下一个客户端连接
阻塞式迭代模式步骤 1 2 3 4 5 6 7 8 9 10 SOCKET Bind_Listen (int nBacklog) SOCKET AcceptConnetion (SOCKET hSocket) BOOL ClientConFun (SOCKET sd) BOOL CloseConnect (SOCKET sd) void MyTepSerFun ()
代码 服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib" ) #define PORT 8080 #define BUFSIZE 1024 SOCKET Bind_Listen (int nBacklog) { WSADATA wsaData; int iResult; SOCKET server_fd; struct sockaddr_in address ; iResult = WSAStartup(MAKEWORD(2 , 2 ), &wsaData); if (iResult != 0 ) { printf ("WSAStartup failed: %d\n" , iResult); return INVALID_SOCKET; } if ((server_fd = socket(AF_INET, SOCK_STREAM, 0 )) == INVALID_SOCKET) { perror("socket failed" ); WSACleanup(); return INVALID_SOCKET; } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof (address)) == SOCKET_ERROR) { perror("bind failed" ); closesocket(server_fd); WSACleanup(); return INVALID_SOCKET; } if (listen(server_fd, nBacklog) == SOCKET_ERROR) { perror("listen" ); closesocket(server_fd); WSACleanup(); return INVALID_SOCKET; } return server_fd; } SOCKET AcceptConnetion (SOCKET hSocket) { struct sockaddr_in client_addr ; int addrlen = sizeof (client_addr); SOCKET new_socket = accept(hSocket, (struct sockaddr *)&client_addr, &addrlen); if (new_socket == INVALID_SOCKET) { perror("accept" ); } return new_socket; } BOOL ClientConFun (SOCKET sd) { char buffer[BUFSIZE] = {0 }; const char *hello = "Hello from server" ; int iResult; iResult = recv(sd, buffer, BUFSIZE, 0 ); if (iResult > 0 ) { printf ("Received: %s\n" , buffer); } else if (iResult == 0 ) { printf ("Connection closing...\n" ); return FALSE; } else { perror("recv failed" ); return FALSE; } send(sd, hello, strlen (hello), 0 ); printf ("Hello message sent\n" ); return TRUE; } BOOL CloseConnect (SOCKET sd) { if (closesocket(sd) == SOCKET_ERROR) { perror("closesocket" ); return FALSE; } return TRUE; } void MyTepSerFun () { SOCKET server_fd = Bind_Listen(3 ); if (server_fd == INVALID_SOCKET) { return ; } while (1 ) { SOCKET client_socket = AcceptConnetion(server_fd); if (client_socket != INVALID_SOCKET) { ClientConFun(client_socket); CloseConnect(client_socket); } } closesocket(server_fd); WSACleanup(); } int main () { MyTepSerFun(); return 0 ; }
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #pragma comment(lib, "ws2_32.lib" ) #define PORT 8080 #define BUFSIZE 1024 int main () { WSADATA wsaData; int iResult; SOCKET client_fd; struct sockaddr_in serv_addr ; char buffer[BUFSIZE] = {0 }; const char *hello = "Hello from client" ; iResult = WSAStartup(MAKEWORD(2 , 2 ), &wsaData); if (iResult != 0 ) { printf ("WSAStartup failed: %d\n" , iResult); return 1 ; } if ((client_fd = socket(AF_INET, SOCK_STREAM, 0 )) == INVALID_SOCKET) { perror("socket failed" ); WSACleanup(); return 1 ; } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(PORT); if (inet_pton(AF_INET, "127.0.0.1" , &serv_addr.sin_addr) <= 0 ) { perror("inet_pton failed" ); closesocket(client_fd); WSACleanup(); return 1 ; } if (connect(client_fd, (struct sockaddr *)&serv_addr, sizeof (serv_addr)) == SOCKET_ERROR) { perror("connect failed" ); closesocket(client_fd); WSACleanup(); return 1 ; } send(client_fd, hello, strlen (hello), 0 ); printf ("Hello message sent\n" ); iResult = recv(client_fd, buffer, BUFSIZE, 0 ); if (iResult > 0 ) { printf ("Received: %s\n" , buffer); } else if (iResult == 0 ) { printf ("Connection closed\n" ); } else { perror("recv failed" ); } closesocket(client_fd); WSACleanup(); return 0 ; }
阻塞式并发连接模式 1.通过多线程,可以同时服务多个连接,每一个线程处理一个客户端连接
和迭代模式基本相同,只是在于处理客户端连接上,需要用到多线程处理多线程来处理客户端连接,以给予服务端同时处理业务的能力。
代码 主要是针对服务端的代码,所以这里只写服务端
服务端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #include <process.h> #pragma comment(lib, "ws2_32.lib" ) #define PORT 8080 #define BUFSIZE 1024 unsigned __stdcall ClientThread (void *arg) { SOCKET client_socket = *(SOCKET *)arg; char buffer[BUFSIZE] = {0 }; const char *hello = "Hello from server" ; int iResult; iResult = recv(client_socket, buffer, BUFSIZE, 0 ); if (iResult > 0 ) { printf ("Received: %s\n" , buffer); } else if (iResult == 0 ) { printf ("Connection closing...\n" ); } else { perror("recv failed" ); } send(client_socket, hello, strlen (hello), 0 ); printf ("Hello message sent\n" ); closesocket(client_socket); free (arg); return 0 ; } SOCKET Bind_Listen (int nBacklog) { WSADATA wsaData; int iResult; SOCKET server_fd; struct sockaddr_in address ; iResult = WSAStartup(MAKEWORD(2 , 2 ), &wsaData); if (iResult != 0 ) { printf ("WSAStartup failed: %d\n" , iResult); return INVALID_SOCKET; } if ((server_fd = socket(AF_INET, SOCK_STREAM, 0 )) == INVALID_SOCKET) { perror("socket failed" ); WSACleanup(); return INVALID_SOCKET; } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof (address)) == SOCKET_ERROR) { perror("bind failed" ); closesocket(server_fd); WSACleanup(); return INVALID_SOCKET; } if (listen(server_fd, nBacklog) == SOCKET_ERROR) { perror("listen" ); closesocket(server_fd); WSACleanup(); return INVALID_SOCKET; } return server_fd; } void MyTepSerFun () { SOCKET server_fd = Bind_Listen(3 ); if (server_fd == INVALID_SOCKET) { return ; } while (1 ) { struct sockaddr_in client_addr ; int addrlen = sizeof (client_addr); SOCKET *client_socket = (SOCKET *)malloc (sizeof (SOCKET)); *client_socket = accept(server_fd, (struct sockaddr *)&client_addr, &addrlen); if (*client_socket == INVALID_SOCKET) { perror("accept" ); free (client_socket); continue ; } _beginthreadex(NULL , 0 , ClientThread, (void *)client_socket, 0 , NULL ); } closesocket(server_fd); WSACleanup(); } int main () { MyTepSerFun(); return 0 ; }
非阻塞式 代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #include <process.h> #pragma comment(lib, "ws2_32.lib" ) #define PORT 8080 #define BUFSIZE 1024 unsigned __stdcall ClientThread (void *arg) { SOCKET client_socket = *(SOCKET *)arg; char buffer[BUFSIZE] = {0 }; const char *hello = "Hello from server" ; int iResult; u_long iMode = 1 ; iResult = ioctlsocket(client_socket, FIONBIO, &iMode); if (iResult != NO_ERROR) { perror("ioctlsocket failed" ); closesocket(client_socket); free (arg); return 1 ; } while (1 ) { iResult = recv(client_socket, buffer, BUFSIZE, 0 ); if (iResult > 0 ) { printf ("Received: %s\n" , buffer); send(client_socket, hello, strlen (hello), 0 ); printf ("Hello message sent\n" ); } else if (iResult == 0 ) { printf ("Connection closing...\n" ); break ; } else { if (WSAGetLastError() == WSAEWOULDBLOCK) { Sleep(100 ); continue ; } else { perror("recv failed" ); break ; } } } closesocket(client_socket); free (arg); return 0 ; } SOCKET Bind_Listen (int nBacklog) { WSADATA wsaData; int iResult; SOCKET server_fd; struct sockaddr_in address ; iResult = WSAStartup(MAKEWORD(2 , 2 ), &wsaData); if (iResult != 0 ) { printf ("WSAStartup failed: %d\n" , iResult); return INVALID_SOCKET; } if ((server_fd = socket(AF_INET, SOCK_STREAM, 0 )) == INVALID_SOCKET) { perror("socket failed" ); WSACleanup(); return INVALID_SOCKET; } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof (address)) == SOCKET_ERROR) { perror("bind failed" ); closesocket(server_fd); WSACleanup(); return INVALID_SOCKET; } if (listen(server_fd, nBacklog) == SOCKET_ERROR) { perror("listen" ); closesocket(server_fd); WSACleanup(); return INVALID_SOCKET; } return server_fd; } void MyTepSerFun () { SOCKET server_fd = Bind_Listen(3 ); if (server_fd == INVALID_SOCKET) { return ; } while (1 ) { struct sockaddr_in client_addr ; int addrlen = sizeof (client_addr); SOCKET *client_socket = (SOCKET *)malloc (sizeof (SOCKET)); *client_socket = accept(server_fd, (struct sockaddr *)&client_addr, &addrlen); if (*client_socket == INVALID_SOCKET) { if (WSAGetLastError() == WSAEWOULDBLOCK) { Sleep(100 ); continue ; } else { perror("accept" ); free (client_socket); continue ; } } _beginthreadex(NULL , 0 , ClientThread, (void *)client_socket, 0 , NULL ); } closesocket(server_fd); WSACleanup(); } int main () { MyTepSerFun(); return 0 ; }
Select模型 Select模型对应的是一个函数
从字面意思理解,select可从诸多连接中检测出可读的(accpet函数),也就是有响应的连接;也可以从诸多连接中检测出可写的(recv、send函数),也就是可以发送消息的连接。
select模型逻辑 select模型逻辑步骤如下:
将所有的socket装进一个数组中
通过select函数遍历socket数组,取出有响应(可读、可写)的socket放进另一个数组
对存入有响应的socket数组处理
函数和结构体 Select函数:
1 2 3 4 5 6 7 int select ( int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout ) ;
返回值 1,如果超时,返回值 0,出现错误,返回值Socket_ERROR
fd_set结构体
1 2 3 4 5 typedef struct fd_set { u_int fd_count; SOCKET fd_array[FD_SETSIZE]; }fd_set; #define FDZ_SETSIZE 64
因为#define FDZ_SETSIZE 64
所以最大不能超过64个,代表着可以并发处理64个套接字
如果想装更多的socket,可以通过在winsock2.h头文件前声明宏 ,给一个更大的值:
1 2 #define FD_SETSIZE 128 #include <WinSock2.h>
四个操作fd_set的操作宏
操作宏
作用
代码
FD_ZERO
将客户端socket集合清零
FD_ZERO(&clientSockets);
FD_SET
添加一个socket(超过默认值大小不再处理)
FD_SET(socketListen, &clientSockets);
FD_CLR
从集合中删除指定的socket一定要close,手动释放
FD_CLR(socketListen,&clientSockets);
closesocket(socketListen);
FD_ISSET
查询socket是否在集合中,不存在返回0,存在返回非0
FD_ISSET(socketListen, &clientSockets);
getsockopt函数
1 2 3 4 5 6 7 int getsockopt ( SOCKET s; int level; int optname; char FAR *optval; int FAR *optlen ) ;
成功返回0;失败返回SOCKET_ERROR
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 int main (int argc, char * argv[]) { WORD wsVersion = MAKEWORD(2 , 2 ); WSADATA wsaData = {0 }; WSAStartup(wsVersion, &wsaData); SOCKET sSocket = socket(AF_INET, SOCK_STREAM, 0 ); if (SOCKET_ERROR == sSocket) { printf ("套接字闯创建失败!\n" ); } else { printf ("套接字闯创建成功!\n" ); } sockaddr_in sockAddrInfo = {0 }; sockAddrInfo.sin_addr.S_un.S_addr = inet_addr("127.0.0.1" ); sockAddrInfo.sin_port = htons(2118 ); sockAddrInfo.sin_family = AF_INET; int bRes = bind(sSocket, (sockaddr*)&sockAddrInfo, sizeof (sockAddrInfo)); if (SOCKET_ERROR == bRes) { printf ("绑定失败!\n" ); } else { printf ("绑定成功!\n" ); } int lRes = listen(sSocket, 5 ); if (SOCKET_ERROR == lRes) { printf ("监听失败!\n" ); } else { printf ("监听成功!\n" ); } fd_set fdSocket; FD_ZERO(&fdSocket); FD_SET(sSocket, &fdSocket); while (true ) { fd_set readfds = fdSocket; fd_set writefds = fdSocket; int iRes = select(0 , &readfds, &writefds, NULL , NULL ); if (iRes > 0 ) { for (u_int i = 0 ; i < writefds.fd_count; i++) { send(readfds.fd_array[i], "Hello" , 6 , 0 ); } for (i = 0 ; i < readfds.fd_count; i++) { if (readfds.fd_array[i] == sSocket) { sockaddr_in s = {0 }; int l = sizeof (s); SOCKET aSocket = accept(sSocket, (sockaddr*)&s, &l); if (INVALID_SOCKET == aSocket) { continue ; } FD_SET(aSocket, &fdSocket); printf ("Accpet Client IP: %s \n" , inet_ntoa(s.sin_addr)); } else { char buf[100 ] = {0 }; int iRecv = recv(readfds.fd_array[i], buf, sizeof (buf), 0 ); if (iRecv > 0 ) { printf ("Recv: %s \n" , buf); } else { SOCKET tSocket = readfds.fd_array[i]; FD_CLR(tSocket, &fdSocket); shutdown(tSocket, SD_BOTH); closesocket(tSocket); } } } } else { continue ; } } closesocket(sSocket); WSACleanup(); return 0 ; }
WSAAsyncSelect模型 WSAAsyncSelect模式允许以windows消息的形式接收网络事件通知。这个模式是为了适应windows消息驱动环境而设置的,对性能要求不高的网络应用程序可采用此模式。
优缺点:WSAAsyncSelect模型最突出的特点是与windows的消息驱动机制融在了一起 ,这使得开发带GUI界面的网络程序 变得很简单。但是如果连接增加,单个WINDOWS函数处理上千个客户请求时,服务器性能势必发受到影响。
WSAAsyncSelect()函数自动把套接字设为非阻塞模式,并且为套接字绑定一个窗口句柄,当有网络事件发生时,便向这个窗口发送消息。
语法:
1 2 3 4 5 6 int WSAAPI WSAAsyncSelect ( [in] SOCKET s, [in] HWND hWnd, [in] u_int wMsg, [in] long lEvent ) ;
lEvent 参数:
值
含义
FD_READ
欲接收 读准备好 的通知.
FD_WRITE
欲接收 写准备好 的通知.
FD_OOB
欲接收 带边数据到达 的通知.
FD_ACCEPT
欲接收 将要连接 的通知.
FD_CONNECT
欲接收 已连接好 的通知.
FD_CLOSE
欲接收 套接口关闭 的通知.
服务端示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 #include <windows.h> #include <winsock2.h> #include <stdio.h> #pragma comment(lib, "ws2_32.lib" ) #define PORT 8080 #define WM_SOCKET WM_USER + 1 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wcex; HWND hwnd; MSG msg; SOCKET server_fd; struct sockaddr_in address ; WSADATA wsaData; int iResult = WSAStartup(MAKEWORD(2 , 2 ), &wsaData); if (iResult != 0 ) { printf ("WSAStartup failed: %d\n" , iResult); return 1 ; } wcex.cbSize = sizeof (WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0 ; wcex.cbWndExtra = 0 ; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(NULL , IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL , IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1 ); wcex.lpszMenuName = NULL ; wcex.lpszClassName = "WSAAsyncSelectExample" ; wcex.hIconSm = LoadIcon(NULL , IDI_APPLICATION); if (!RegisterClassEx(&wcex)) { printf ("RegisterClassEx failed: %d\n" , GetLastError()); return 1 ; } hwnd = CreateWindow("WSAAsyncSelectExample" , "WSAAsyncSelect Example" , WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0 , CW_USEDEFAULT, 0 , NULL , NULL , hInstance, NULL ); if (!hwnd) { printf ("CreateWindow failed: %d\n" , GetLastError()); return 1 ; } ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); if ((server_fd = socket(AF_INET, SOCK_STREAM, 0 )) == INVALID_SOCKET) { printf ("socket failed: %d\n" , WSAGetLastError()); WSACleanup(); return 1 ; } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof (address)) == SOCKET_ERROR) { printf ("bind failed: %d\n" , WSAGetLastError()); closesocket(server_fd); WSACleanup(); return 1 ; } if (listen(server_fd, 3 ) == SOCKET_ERROR) { printf ("listen failed: %d\n" , WSAGetLastError()); closesocket(server_fd); WSACleanup(); return 1 ; } if (WSAAsyncSelect(server_fd, hwnd, WM_SOCKET, FD_ACCEPT | FD_READ | FD_CLOSE) == SOCKET_ERROR) { printf ("WSAAsyncSelect failed: %d\n" , WSAGetLastError()); closesocket(server_fd); WSACleanup(); return 1 ; } while (GetMessage(&msg, NULL , 0 , 0 )) { TranslateMessage(&msg); DispatchMessage(&msg); } closesocket(server_fd); WSACleanup(); return (int )msg.wParam; } LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { SOCKET client_fd; char buffer[1024 ]; int iResult; switch (msg) { case WM_SOCKET: if (WSAGETSELECTERROR(lParam)) { printf ("Socket error: %d\n" , WSAGETSELECTERROR(lParam)); closesocket((SOCKET)wParam); break ; } switch (WSAGETSELECTEVENT(lParam)) { case FD_ACCEPT: client_fd = accept((SOCKET)wParam, NULL , NULL ); if (client_fd == INVALID_SOCKET) { printf ("accept failed: %d\n" , WSAGetLastError()); } else { if (WSAAsyncSelect(client_fd, hwnd, WM_SOCKET, FD_READ | FD_CLOSE) == SOCKET_ERROR) { printf ("WSAAsyncSelect failed: %d\n" , WSAGetLastError()); closesocket(client_fd); } } break ; case FD_READ: iResult = recv((SOCKET)wParam, buffer, sizeof (buffer), 0 ); if (iResult > 0 ) { buffer[iResult] = '\0' ; printf ("Received: %s\n" , buffer); send((SOCKET)wParam, buffer, iResult, 0 ); } else if (iResult == 0 ) { printf ("Connection closed\n" ); closesocket((SOCKET)wParam); } else { printf ("recv failed: %d\n" , WSAGetLastError()); closesocket((SOCKET)wParam); } break ; case FD_CLOSE: printf ("Connection closed\n" ); closesocket((SOCKET)wParam); break ; } break ; case WM_DESTROY: PostQuitMessage(0 ); break ; default : return DefWindowProc(hwnd, msg, wParam, lParam); } return 0 ; }