unit 1 理解网络编程和套接字
课后习题
1.套接字在网络编程中的作用是什么?为什么称它为套接字?
套接字是网络数据传输用的软件设备
我们把插头插到插座上就能从电网获得电力供给,同样,为了与远程计算机进行数据传输,需要连接到因特网,而编程中的“套接字”就是用来连接该网络的工具。它本身就带有“连接”的含义,如果将其引申,则还可以表示两台计算机之间的网络连接。
2.在服务器端创建套接字后,会依次调用listen函数和accept函数。请比较并说明两者作用
listen:将套接字转为可接收链接状态
accept:受理链接请求
3.Linux中,对套接字数据进行I/O时可以直接使用I/O相关函数;而在Windows中则不可以。原因为何?
对于Linux而言,socket操作和文件操作没有区别,但是在Windows中socket进行文件操作,则需要调用特殊的数据传输相关函数。
4.创建套接字后一般会给它分配地址,为什么?为了完成地址分配需要调用哪些函数?
区分来自不同机器的套接字,通过bind()分配地址信息(IP地址和端口号)
5.Linux中的文件描述符与Windows的句柄实际上非常类似。请以套接字为对象说明他们的含义。
Linux中的文件描述符是系统分配给文件或套接字的整数
Windows中和Linux概念相同,不过Windows要区分文件句柄和套接字句柄
6.底层文件I/O函数与ANSI标准定义的文件I/O函数之间有何区别?
文件I/O 又称为低级磁盘I/O,遵循POSIX相关标准。任何兼容POSIX标准的操作系统上都支持文件I/O。标准I/O被称为高级磁盘I/O,遵循ANSI C相关标准。只要开发环境中有标准I/O库,标准I/O就可以使用。(Linux 中使用的是GLIBC,它是标准C库的超集。不仅包含ANSI C中定义的函数,还包括POSIX标准中定义的函数。因此,Linux 下既可以使用标准I/O,也可以使用文件I/O)。
通过文件I/O读写文件时,每次操作都会执行相关系统调用。这样处理的好处是直接读写实际文件,坏处是频繁的系统调用会增加系统开销,标准I/O可以看成是在文件I/O的基础上封装了缓冲机制。先读写缓冲区,必要时再访问实际文件,从而减少了系统调用的次数。
文件I/O中用文件描述符表现一个打开的文件,可以访问不同类型的文件如普通文件、设备文件和管道文件等。而标准I/O中用FILE(流)表示一个打开的文件,通常只用来访问普通文件。
7.参考本书给出的示例low_open.c和low_read.c,分别利用底层文件I/O和ANSI标准I/O编写文件复制程序。可任意指定复制程序的使用方法
服务端
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hServSock, hClntSock; SOCKADDR_IN servAddr, clntAddr; FILE* fp; int strLen; int szClntAddr; char message[100]; char filename[30]; if (argc != 2) { printf("Usage:%s <port>\n", argv[0]); system("PAUSE"); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { ErrorHandling("WSAStartup Error!\n"); } hServSock = socket(PF_INET, SOCK_STREAM, 0); if (hServSock == INVALID_SOCKET) { ErrorHandling("sock() error!\n"); } servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = htonl(INADDR_ANY); servAddr.sin_port = htons(atoi(argv[1])); if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) { ErrorHandling("bind() error!\n"); } if (listen(hServSock, 5) == SOCKET_ERROR) { ErrorHandling("listen() error!\n"); } szClntAddr = sizeof(clntAddr); hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr); if (hClntSock == INVALID_SOCKET) { ErrorHandling("accept() error!\n"); } printf("请输入文件名:"); scanf("%s", &filename); send(hClntSock, filename, sizeof(filename), 0); strLen = recv(hClntSock, message, sizeof(message) - 1, 0); if (strLen == -1) { ErrorHandling("recv() error!"); } if ((fp = fopen(filename, "w+")) == NULL) { ErrorHandling("fopen() error!"); } fputs(message, fp); fclose(fp); closesocket(hServSock); closesocket(hClntSock); WSACleanup(); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
客户端
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib,"ws2_32.lib") void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hSocket; SOCKADDR_IN servAddr; int strLen; FILE* fp; char message[100] = { 0 }; char tmpmessage[100] = { 0 }; char filename[30]; if (argc != 3) { printf("Usage: %s <IP><port>\n", argv[0]); system("PAUSE"); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { ErrorHandling("WSAStartup() error!"); } hSocket = socket(PF_INET, SOCK_STREAM, 0); if (hSocket == INVALID_SOCKET) { ErrorHandling("socket() error!"); } memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = inet_addr(argv[1]); servAddr.sin_port = htons(atoi(argv[2])); if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) { ErrorHandling("connect() error!"); } strLen = recv(hSocket, filename, sizeof(filename) - 1, 0); if (strLen == -1) { ErrorHandling("recv() error!"); } if ((fp = fopen(filename, "r")) == NULL) { ErrorHandling("fopen() error!"); } while (fgets(tmpmessage, 255, fp) != NULL) { strcat(message, tmpmessage); } message[strlen(message)] = '\0'; send(hSocket, message, sizeof(message), 0); fclose(fp); closesocket(hSocket); WSACleanup(); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
unit 2 套接字类型与协议
课后习题
1.什么是协议?在收发数据中定义协议有何意义?
为了完成数据交换而定好的约定。
对数据传输中所需承诺进行定义。
2.面向连接的TCP套接字传输特性有3点,请分别说明。
- 传输过程中数据不会消失。
- 按序传输数据。
- 传输的数据不存在数据边界(Boundary)
3.面哪些是面向消息的套接字的特性?
a,c,e
4.下列数据适合用哪类套接字传输?并给出原因。
UDP,需要快速传输
TCP,传输过程中数据不会消失
TCP,传输过程中数据不会消失
5.何种类型的套接字不存在数据边界?这类套接字接收数据时需要注意什么?
面向连接的套接字(SOCK_STREAM),保证缓冲区数据不会被填满,或者传输速度不大于读取速度。
6.tcp_server.c和ltcp_client.c中需多次调用read函数读取服务器端调用1次write函数传递的字符串。更改程序,使服务器端多次调用(次数自拟)write函数传输数据,客户端调用1次read函数进行读取。为达到这一目的,客户端需延迟调用read函数,因为客户端要等待服务器端传输所有数据。Windows和Linux都通过下列代码延迟read或recv函数的调用。
服务端
#include <stdlib.h> #include <stdio.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib,"ws2_32.lib") void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hServSock, hClntSock; SOCKADDR_IN servAddr, clntAddr; int szClntAddr = 0, idx = 0, send_len = 0; char message[100] = "hello world!"; if (argc != 2) { printf("Usage:%s <port>\n", argv[0]); system("PAUSE"); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { ErrorHandling("WSAStartup Error!"); } hServSock = socket(PF_INET, SOCK_STREAM, 0); if (hServSock == INVALID_SOCKET) { ErrorHandling("socket() error!"); } servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = htonl(INADDR_ANY); servAddr.sin_port = htons(atoi(argv[1])); if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) { ErrorHandling("bind() error!"); } if (listen(hServSock, 5) == SOCKET_ERROR) { ErrorHandling("listen() error!"); } szClntAddr = sizeof(clntAddr); hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr); if (hClntSock == INVALID_SOCKET) { ErrorHandling("accept() error!"); } while (message[idx-1] != '\0') { send(hClntSock, &message[idx++], 1, 0); } closesocket(hServSock); closesocket(hClntSock); WSACleanup(); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
客户端
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hServSock, hClntSock; SOCKADDR_IN servAddr, clntAddr; int szClntAddr,idx = 0, send_len = 0; char message[100] = "hello world!"; if (argc != 2) { printf("Usage:%s <port>\n", argv[0]); system("PAUSE"); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { ErrorHandling("WSAStartup Error!"); } hServSock = socket(PF_INET, SOCK_STREAM, 0); if (hServSock == INVALID_SOCKET) { ErrorHandling("sock() error!"); } servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = htonl(INADDR_ANY); servAddr.sin_port = htons(atoi(argv[1])); if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) { ErrorHandling("bind() error!"); } if (listen(hServSock, 5) == SOCKET_ERROR) { ErrorHandling("listen() error!"); } szClntAddr = sizeof(clntAddr); hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr); if (hClntSock == INVALID_SOCKET) { ErrorHandling("accept() error!"); } send(hClntSock, message, sizeof(message), 0); closesocket(hServSock); closesocket(hClntSock); WSACleanup(); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
unit 3 地址族与数据序列
课后习题
1.IP地址族IPv4和IPv6有何区别?在何种背景下诞生了IPv6?
IPv4使用4字节地址族,IPv6使用16字节地址族。
IPv6是为了应对2010年前后IP地址耗尽的问题而提出的标准。
2.通过IPV4网络ID、主机ID及路由器的关系说明向公司局域网中的计算机传输数据的过程
传输数据首先通过IPv4的网络ID传输到网络中,路由器接受到数据后,通过主机ID传输给目标计算机。
3.套接字地址分为IP地址和端口号。为什么需要IP地址和端口号?或者说,通过IP可以区分哪些对象?通过端口号可以区分哪些对象?
IP用于区分计算机,端口号用于区分同一操作系统中的不同套接字。
4.请说明IP地址的分类方法,并据此说出下面这些IP地址的分类。
根据IP地址的边界区分网络地址
C,A,B
5.计算机通过路由器或交换机连接到互联网。请说出路由器和交换机的作用
路由器和交换机是数据传输的中介。
6.什么是知名端口?其范围是多少?知名端口中具有代表性的HTTP合同FTP端口号各是多少?
那些由互联网名称与数字地址分配机构(ICANN)预留给传输控制协议(TCP)和用户数据包协议(UDP)使用的端口号。
0~1023
80和21
7.题目大概意思是:为什么bind中第二个参数是sockaddr,但是传入的是sockaddr_in
sockaddr中sa_data保存IP和端口号会很麻烦,因为sockaddr_in和sockaddr结构体结构相同,因此可以通过强制转换为sockaddr类型,传递给bind函数。
8.请解释大端序、小端序、网络字节序,并说明为何需要网络字节序
大端序:高位字节存储到低位上,低位字节存储到高位上
小端序:高位字节存储到高位上,低位字节存储到低位上
网络字节序:大端序
为网络数据传输制定标准。
9.大端计算机希望将4字节整型数据12传到小端序计算机。请说出数据传输过程中发生的字节序变换过程
因为计算机为大端序(00 00 00 0c),因此传输到网络中不需要交换顺序(00 00 00 0c),从网络将数据传输到小端计算机需要交换顺序(0c 00 00 00)
10.怎么表示回送地址?其含义是什么?如果向回送地址传输数据将会发生什么情况?
回送地址就是计算机本身的地址127.0.0.1,
不会经过网络传输数据,数据会直接返回。
unit 4 基于TCP的服务器端/客户端(1)
课后习题
1.请说明TCP/IP的4层协议栈,并说明TCP和UDP套接字经过的层级结构差异
链路层 IP层 TCP层 应用层
UDP和TCP第三层不同,UDP为UDP层
2.请说出TCP/IP协议栈中链路层和IP层的作用,并给出两者关系。
链路层:专门定义LAN,WAN,MAN等网络标准,是物理链接领域标准化的结果。
IP层:定义网络数据传输路径的层级。
IP层负责以链路层为基础的数据传输。
3.为何需要把TCP/IP协议栈分成4层(或7层)?结合开放式系统回答
将复杂的TCP/IP协议分层化的话,就可以将分层的层级标准发展成开放系统。实际上,TCP/IP是开放系统,各层级都被初始化,并以该标准为依据组成了互联网。因此,按照不同层级标准,硬件和软件可以相互替代,这种标准化是TCP/IP蓬勃发张的依据。
4.客户端调用connect函数向服务器端发送连接请求。服务器端调用哪个函数后,客户端可以调用connect函数?
listen
5.什么时候创建连接请求等待队列?它有何作用?与accept有什么关系
listen
创建请求等待队列
accept函数受力连接请求等待队列中待处理的客户端连接请求。
6.客户端中为何不需要调用bind函数分配地址?如果不调用bind函数,那何时、如何向套接字分配IP地址和端口号?
客户端的IP地址和端口在调用connect函数时自动分配,无需调用标记的bind函数进行分配。
何时:调用connect函数时,如何:IP用计算机(主机)的IP,端口随机。
7.改成迭代服务器端
服务端
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hServSock, hClntSock; SOCKADDR_IN servAddr, clntAddr; int szClntAddr,idx = 0, send_len = 0; char message[100] = "hello world!"; int strLen; if (argc != 2) { printf("Usage:%s <port>\n", argv[0]); system("PAUSE"); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { ErrorHandling("WSAStartup Error!"); } hServSock = socket(PF_INET, SOCK_STREAM, 0); if (hServSock == INVALID_SOCKET) { ErrorHandling("sock() error!"); } servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = htonl(INADDR_ANY); servAddr.sin_port = htons(atoi(argv[1])); if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) { ErrorHandling("bind() error!"); } if (listen(hServSock, 5) == SOCKET_ERROR) { ErrorHandling("listen() error!"); } szClntAddr = sizeof(clntAddr); hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr); if (hClntSock == INVALID_SOCKET) { ErrorHandling("accept() error!"); } while (1) { send(hClntSock, message, sizeof(message), 0); strLen = recv(hClntSock, message, sizeof(message), 0); if (strLen == -1) ErrorHandling("recv() error!"); printf("Message from client:%s\n", message); //closesocket(hClntSock); } closesocket(hClntSock); closesocket(hServSock); WSACleanup(); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
客户端
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #include <Windows.h> #pragma warning(disable:4996) #pragma comment(lib,"ws2_32.lib") void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hSocket; SOCKADDR_IN servAddr; int strLen = 0, recv_len = 0; char message[100] = { 0 }; if (argc != 3) { printf("Usage: %s <IP><port>\n", argv[0]); system("PAUSE"); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { ErrorHandling("WSAStartup() error!"); } hSocket = socket(PF_INET, SOCK_STREAM, 0); if (hSocket == INVALID_SOCKET) { ErrorHandling("socket() error!"); } memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = inet_addr(argv[1]); servAddr.sin_port = htons(atoi(argv[2])); if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR) { ErrorHandling("connect() error!"); } //Sleep(3000); while (1) { recv_len = recv(hSocket, message, sizeof(message), 0); if (recv_len == -1) ErrorHandling("recv() error!"); printf("Message from server:%s\n", message); send(hSocket, message, sizeof(message), 0); Sleep(1000); } closesocket(hSocket); WSACleanup(); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
unit 5 基于TCP的服务器端/客户端(2)
练习
客户端(op_client.c)
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #define N 1024 #pragma warning(disable:4996) #pragma comment(lib,"ws2_32.lib") void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET serv_sock; SOCKADDR_IN serv_addr; int i; if(argc != 3) { printf("Usage: %s <IP> <port>\n", argv[0]); system("PAUSE"); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStarup error!"); serv_sock = socket(PF_INET, SOCK_STREAM, 0); if (serv_sock == INVALID_SOCKET) ErrorHandling("socket() error!"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); if (connect(serv_sock, (struct sockaddr*) & serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) ErrorHandling("connect() error"); else puts("Connected... ...\n"); while (1) { char a[N] = { 0 }, s[10] = { 0 }, result[10] = { 0 }; int op_count = 0; fputs("operand count:", stdout); scanf("%d", &op_count); for (i = 0; i < op_count; ++i) { fputs("Operand:", stdout); scanf("%s", &a[i*5]); } fputs("Operator:", stdout); scanf("%s", &a[op_count*5]); if (send(serv_sock, a, N, 0) == -1) ErrorHandling("send() error!"); int flag = recv(serv_sock, result, 10, 0); if ( flag == -1 ) ErrorHandling("recv() error!"); if (flag != 0) { printf("Operation:%s\n", result); Sleep(1000); } fputs("Continue?(Y or N):", stdout); scanf("%s", s); if (!strcmp(s,"n") || !strcmp(s, "N")) break; memset(a, 0, N); } closesocket(serv_sock); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
服务端(op_server.c)
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #define N 1024 #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") void ErrorHandling(const char* message); int computer(int* num, char* arr, int n); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKADDR_IN serv_addr, clnt_addr; SOCKET serv_sock, clnt_sock; int i, strLen = 0, sz = 0; char a[N] = { 0 }; int numarr[N] = { 0 }; if (argc != 2) { printf("Usage: %s<port>", argv[0]); system("PAUSE"); return 0; } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error!"); serv_sock = socket(PF_INET, SOCK_STREAM, 0); if (serv_sock == INVALID_SOCKET) ErrorHandling("socket() error!"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1])); if (bind(serv_sock, (struct sockaddr*) & serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) ErrorHandling("bind() error!"); if (listen(serv_sock, 5) == SOCKET_ERROR) ErrorHandling("listen() error!"); sz = sizeof(clnt_addr); clnt_sock = accept(serv_sock, (struct sockaddr*) & clnt_addr, &sz); if (clnt_sock == -1) ErrorHandling("accept() error!"); while (1) { char result[10] = { 0 }; while ((strLen = recv(clnt_sock, a, N, 0)) != 0) { //printf("message from client:"); if (strLen > 0) { for (i = 0; i < N; i += 5) { if (a[i + 5] == '\0') { itoa(computer(numarr, a, i), result,10); send(clnt_sock, result, 10, 0); break; } //printf("%s ", &a[i]); numarr[i / 5] = atoi(&a[i]); } memset(a, 0, N); break; } } //closesocket(clnt_sock); } closesocket(serv_sock); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); } int computer(int* num, char* arr, int n) { int i, result = num[0]; //for (i = 0; i < 10; ++i) { // printf("%d\t", num[i]); // printf("\n%s\n", &arr[n]); //} if (!strcmp(&arr[n], "+")) { for (i = 1; i < 10; ++i) result += num[i]; } else if (!strcmp(&arr[n], "-")) { for (i = 1; i < 10; ++i) result -= num[i]; } else { printf("error!\n"); } printf("%d\n", result); return result; }
课后习题
1.请说明TCP套接字连接设置的三次握手过程。尤其是3次数据交换过程每次收发的数据内容。
A:SEQ:1000, ACK:-
B:SEQ:2000,ACK:1001
A:SEQ:1001,ACK:2001
2.TCP是可靠的数据传输协议,但在通过网络通信的过程可能丢失数据。请通过ACK和SEQ说明TCP通过何种机制保证丢失数据的可靠传输。
通过SEQ传输信息编号,ACK通过信息编号返回编号信息。
3.TCP套接字中调用write和read函数时数据如何移动?结合I/O缓冲进行说明
write将数据写入输出缓存区,再通过网络传输将输出缓存区数据传输到输入缓存区,read从输入缓存区读取数据。
4.对方主机的输入缓冲剩余50字节空间时,若本方主机通过write函数请求传输70字节,问TCP如何处理这种情况?
多出的数据会在输入缓存区中等待。
5.第2章示例tcp_server.c....
tcp_server.c
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #define BUF_SIZE 1024 #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKADDR_IN serv_addr, clnt_addr; SOCKET hServSock, hClntSock; char message[BUF_SIZE] = { "Hello World!" }; int i = 0, strLen = 0; if (argc != 2) { printf("Usage: %s <port>", argv[0]); system("PAUSE"); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStarup() error!"); hServSock = socket(PF_INET, SOCK_STREAM, 0); if (hServSock == INVALID_SOCKET) ErrorHandling("socket() error!"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(atoi(argv[1])); if (bind(hServSock, (struct sockaddr*) & serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) ErrorHandling("bind() error!"); if (listen(hServSock, 5) == SOCKET_ERROR) ErrorHandling("listen() error!"); strLen = sizeof(clnt_addr); hClntSock = accept(hServSock, (struct sockaddr*) & clnt_addr, &strLen); if (hClntSock == SOCKET_ERROR) ErrorHandling("accept() error!"); for (; i < 3; ++i) { strLen = strlen(message) + 1; send(hClntSock, (char*)&strLen, 4, 0); send(hClntSock, message, strLen, 0); memset(message, 0, strLen); recv(hClntSock, (char*)&strLen, 4, 0); recv(hClntSock, message, strLen, 0); printf("Message from client: %s\n", message); } closesocket(hClntSock); closesocket(hServSock); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
tcp_client.c
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #define BUF_SIZE 1024 #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hSock; SOCKADDR_IN serv_addr; int i = 0, strLen = 0; char message[BUF_SIZE] = { 0 }; if (argc != 3) { printf("Usage: %s <IP><port>\n", argv[0]); system("PAUSE"); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error!"); hSock = socket(PF_INET, SOCK_STREAM, 0); if (hSock == INVALID_SOCKET) ErrorHandling("socket() error!"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(atoi(argv[2])); if (connect(hSock, (struct sockaddr*) & serv_addr, sizeof(serv_addr)) == SOCKET_ERROR) ErrorHandling("connect() error!"); for (; i < 3; ++i) { recv(hSock, (char*)&strLen, 4, 0); recv(hSock, message, strLen, 0); printf("Message from server: %s\n", message); strLen = strlen(message) + 1; send(hSock, (char*)&strLen, 4, 0); send(hSock, message, strLen, 0); memset(message, 0, strLen); } closesocket(hSock); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
7.创建手法文件的服务端/客户端,实现顺序如下。
- 客户端接受用户输入的传输文件名。
- 客户端请求服务器端传输该文件名所指的文件。
- 如果指定文件存在,服务端就将其发送给客户端;反之,则断开连接。
file_server.c
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #define BUF_SIZE 1024 #define FILE_NAME_SIZE 100 #pragma warning(disable:4996) #pragma comment(lib,"ws2_32.lib") void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hServSock, hClntSock; SOCKADDR_IN hServAddr, hClntAddr; int strLen = 0, i = 0; char FileArr[BUF_SIZE] = { 0 }, FileName[FILE_NAME_SIZE] = { 0 }; FILE* fp; if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); system("PAUSE"); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error!"); hServSock = socket(PF_INET, SOCK_STREAM, 0); if (hServSock == INVALID_SOCKET) ErrorHandling("socket() error!"); memset(&hServAddr, 0, sizeof(hServAddr)); hServAddr.sin_family = AF_INET; hServAddr.sin_addr.s_addr = htonl(INADDR_ANY); hServAddr.sin_port = htons(atoi(argv[1])); if (bind(hServSock, (struct sockaddr*) & hServAddr, sizeof(hServAddr)) == SOCKET_ERROR) ErrorHandling("bind() error!"); if (listen(hServSock, 5) == SOCKET_ERROR) ErrorHandling("listen() error!"); strLen = sizeof(hClntAddr); hClntSock = accept(hServSock, (struct sockaddr*) & hClntAddr, &strLen); if (hClntSock == SOCKET_ERROR) ErrorHandling("accept() error!"); if (recv(hClntSock, FileName, FILE_NAME_SIZE, 0) == -1) ErrorHandling("recv() error!"); if ((fp = fopen(FileName, "r")) == NULL) ErrorHandling("Open file error!"); fread(FileArr, sizeof(char), BUF_SIZE, fp); send(hClntSock, FileArr, BUF_SIZE, 0); fclose(fp); closesocket(hClntSock); closesocket(hServSock); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
file_client
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #define BUF_SIZE 1024 #define FILE_NAME_SIZE 100 #pragma warning(disable:4996) #pragma comment(lib,"ws2_32.lib") void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hSock; SOCKADDR_IN hServAddr; char FileName[FILE_NAME_SIZE] = { 0 }, FileArr[BUF_SIZE] = { 0 }; FILE* fp; if (argc != 3) { printf("Usage:%s<IP><port>\n", argv[0]); system("PAUSE"); return 0; } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error!"); hSock = socket(PF_INET, SOCK_STREAM, 0); if (hSock == INVALID_SOCKET) ErrorHandling("socket() error!"); memset(&hServAddr, 0, sizeof(hServAddr)); hServAddr.sin_family = AF_INET; hServAddr.sin_addr.s_addr = inet_addr(argv[1]); hServAddr.sin_port = htons(atoi(argv[2])); if (connect(hSock, (struct sockaddr*) & hServAddr, sizeof(hServAddr)) == SOCKET_ERROR) ErrorHandling("connect error!"); printf("请输入文件名:"); scanf("%s", FileName); send(hSock, FileName, FILE_NAME_SIZE, 0); if (recv(hSock, FileArr, BUF_SIZE, 0) == -1) ErrorHandling("文件打开失败或者不存在!"); if ((fp = fopen(FileName, "w")) == NULL) ErrorHandling("fopen() error!"); fwrite(FileArr, sizeof(char), BUF_SIZE, fp); fclose(fp); closesocket(hSock); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
unit 6 基于UDP的服务器端/客户端
1.UDP为什么比TCP速度快?为什么TCP数据传输可靠而UDP数据传输不可靠?
没有收发数据前后进行的连接设置和清除过程。
没有收发数据过程中为保证可靠性而添加的流控制。
2.
bce
3.UDP数据包向对方主机的UDP套接字传递过程中,IP和UDP分别负责哪些部分?
IP:连接目标机,UDP:数据传输
4.UDP一般比TCP快,但根据交换数据的特点,其差异可大可小。请说明何种情况下UDP的性能优于TCP
一对多,多对多目标传输。
少量数据,多次传输。
5.客户端TCP套接字调用connect函数时自动分配IP和端口号。UDP中不调用bind函数,那何时分配IP和端口号?
sendto
6.TCP客户端必须调用connect函数,而UDP中可以选择性调用。请问,在UDP中调用connect函数有哪些好处?
在长时间多次与同一主机传输数据中,减少套接字的创建与删除,数据传输速率得到提高。
7.收发的消息均要输出到控制台窗口
client
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SIZE 1024 void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET servSock; int strLen = 0; char message[BUF_SIZE] = { 0 }; SOCKADDR_IN servAddr; if (argc != 3) { printf("Usage:%s<IP><port>\n", argv[0]); system("PAUSE"); return 0; } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() Error!"); servSock = socket(PF_INET, SOCK_DGRAM, 0); if (servSock == INVALID_SOCKET) ErrorHandling("socket() Error!"); memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = inet_addr(argv[1]); servAddr.sin_port = htons(atoi(argv[2])); connect(servSock, (struct sockaddr*) & servAddr, sizeof(servAddr)); while (1) { fputs("input message(q to quit):", stdout); fgets(message, BUF_SIZE - 1, stdin); if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) break; send(servSock, message, strlen(message), 0); memset(message, 0, BUF_SIZE); recv(servSock, message, BUF_SIZE, 0); printf("message from server:%s\n", message); memset(message, 0, BUF_SIZE); } system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
server
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SIZE 1024 void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET servSock; char message[BUF_SIZE] = { 0 }; int strLen = 0, clntAddrsz; SOCKADDR_IN servAddr, clntAddr; if (argc != 2) { printf("Usage:%s<port>\n", argv[0]); system("PAUSE"); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() Error!"); servSock = socket(PF_INET, SOCK_DGRAM, 0); if (servSock == INVALID_SOCKET) ErrorHandling("socket() Error!"); memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = htonl(INADDR_ANY); servAddr.sin_port = htons(atoi(argv[1])); if (bind(servSock, (struct sockaddr*) & servAddr, sizeof(servAddr)) == SOCKET_ERROR) ErrorHandling("bind() Error!"); while (1) { clntAddrsz = sizeof(clntAddr); strLen = recvfrom(servSock, message, BUF_SIZE, 0, (struct sockaddr*) & clntAddr, &clntAddrsz); printf("message from client:%s", message); memset(message, 0, BUF_SIZE); fputs("input message(q to quit):", stdout); fgets(message, BUF_SIZE - 1, stdin); if(!strcmp(message, "q\n") || !strcmp(message, "Q\n")) break; sendto(servSock, message, BUF_SIZE, 0, (struct sockaddr*) & clntAddr, clntAddrsz); memset(message, 0, BUF_SIZE); } closesocket(servSock); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
unit 7 优雅地断开套接字连接
1.解释TCP中“流”的概念。UDP中能否形成流?请说明原因
指套接字建立连接,传输数据的过程。
不能,因为UDP不需要建立连接,通过完整数据包直接传输数据。
2.Linux中的close函数或Windows中的closesocket函数属于单方面断开连接的方法,有可能带来一些问题。什么是单方面断开连接?什么情况下会出现问题?
服务器和客户端都无法接收或者发送数据,当服务器/客户端在传输数据到客户端/服务器时,客户端/服务器断开连接,会造成数据丢失。
3.什么是半关闭?针对输出流执行半关闭的主机处于何种状态?半关闭会导致对方主机接收什么信息?
指只关闭输入或者输出流,若输出流关闭,则无法发送数据,但是可以接收数据。
unit 8 域名及网络地址
1.
b,d
2.
方法可行,当本地网络没有限制时,可以通过设置正常DNS服务器来解析。
3.再浏览器地址输入 www.orentec.co.kr ,并整理出主页显示过程。假设浏览器访问默认 DNS 服务器中并没有关于 www.orentec.co.kr 的地址信息.
- 主机向默认DNS解析域名为IP,无法解析
- 默认服务器向上级DNS服务器询问,直到询问到IP(最多询问到根服务器)
- DNS服务器将IP原路返回到发起请求的主机
unit 9 套接字的多种可选项
习题
1.下列关于Time-wait状态的说法错误的是?
a,c
2.TCP_NODELAY可选项与Nagle算法有关,可通过它禁止Nagle算法。请问何时应考虑禁用Nagle算法?结合收发数据的特性给出说明
在"大文件"的传输上
unit 12 I/O复用
课后练习
1.请解释复用技术的通用含义,并说明何为I/O复用。
在一个通信频道中传递多个数据(信号)的技术
将需要I/O的套接字捆绑在一起,利用最少限度的资源来收发数据的技术
2.多进程并发服务器的缺点有哪些?如何在I/O复用服务器端中弥补?
并发服务器需要大量的运算和内存空间,由于每个进程都具有独立的内存空间,所以相互间的数据交换也要求采用相对复杂的方法。
因为复用服务器是将套接字与I/O捆绑在一起的,因此我们能使用进程管理所有的I/O操作。
3.
bc
4.select函数的观察对象中应包含服务器端套接字(监听套接字),那么应将其包含到哪一类监听对象集合?请说明原因
服务端套接字用来判断是否有请求连接,读取状态,因此应该将其包含到“读取”当中。
5.select函数使用的fd_set结构体在Windows和Linux中具有不同的声明。请说明却别,同时解释存在区别的必然性
Linux的文件描述符从0开始递增,因此可以找出当前文件描述符数量和最后生成的文件描述符之间的关系。但Windows的套接字句柄并非从0开始,而且句柄的整数值之间并无规律可循,因此需要直接保存句柄的数组和记录句柄数的变量。
I/O复用
服务端
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #include <string.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SIZE 1024 void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hServSock, hClntSock; int strLen = 0, fdNum = 0, i; char buf[BUF_SIZE] = { 0 }; fd_set reads, cpyReads; struct timeval timeout; int adrSz = 0; SOCKADDR_IN servAdr, clntAdr; if (argc != 2) { printf("Usage:%s <port>\n", argv[0]); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { ErrorHandling("WSAStartup() Error!"); } hServSock = socket(PF_INET, SOCK_STREAM, 0); if (hServSock == INVALID_SOCKET) { ErrorHandling("socket() Error!"); } memset(&servAdr, 0, sizeof(servAdr)); servAdr.sin_family = AF_INET; servAdr.sin_addr.s_addr = htonl(INADDR_ANY); servAdr.sin_port = htons(atoi(argv[1])); if (bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR) { ErrorHandling("bind() Error!"); } if (listen(hServSock, 5) == SOCKET_ERROR) { ErrorHandling("listen() Error!"); } // 初始化fd_set FD_ZERO(&reads); // PD_SET的第一个参数文件描述符设定为服务器端套接字,这样当服务端有接受数据时,即新的连接请求,即读取数据,就会激活状态。 FD_SET(hServSock, &reads); while (1) { // 拷贝值和设定超时的时间 cpyReads = reads; timeout.tv_sec = 5; timeout.tv_usec = 5000; //调用select函数,判断套接字中是否有数据传输,即新的连接请求,这里SOCKET_ERROR为-1,即当select失败时 if ((fdNum = select(0, &cpyReads, 0, 0, &timeout)) == SOCKET_ERROR) { break; } // 当超时时继续返回循环,监视服务器端套接字 if (fdNum == 0) { continue; } // 当套接字有数据传输时,循环使用FD_ISSET查找发生变化的文件描述符 for (i = 0; i < reads.fd_count; ++i) { if (FD_ISSET(reads.fd_array[i], &cpyReads)) { // 若发生状态变化的文件标识符是服务器套接字,则受理客户端连接请求 if (reads.fd_array[i] == hServSock) { adrSz = sizeof(clntAdr); hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &adrSz); // 注册与客户端连接的套接字,检测客户端连接套接字,当客户端传输数据时,就会激活else,接收数据 FD_SET(hClntSock, &reads); printf("connected client:%d \n", hClntSock); } // 如果服务器端的套接字未发生变化,判断接受的数据表示断开还是字符串 else { strLen = recv(reads.fd_array[i], buf, BUF_SIZE - 1, 0); // 断开连接 if (strLen == 0) { // 从reads中删除该文件描述符套接字 FD_CLR(reads.fd_array[i], &reads); closesocket(cpyReads.fd_array[i]); printf("closed client:%d \n", cpyReads.fd_array[i]); } // 字符串数据 else { send(reads.fd_array[i], buf, strLen, 0); } } } } } closesocket(hServSock); WSACleanup(); system("PAUSE"); return 0; } /** * @brief * @param message */ void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
客户端
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #include <string.h> #define BUF_SIZE 1024 #pragma warning(disable:4996) #pragma comment(lib,"ws2_32.lib") void ErrorHandling(const char* message); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hSocket; char message[BUF_SIZE] = { 0 }; int strLen; SOCKADDR_IN servAddr; if (argc != 3) { printf("Usage: %s <IP><port>\n", argv[0]); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() Error!"); hSocket = socket(PF_INET, SOCK_STREAM, 0); if (hSocket == INVALID_SOCKET) ErrorHandling("socket() error!"); memset(&servAddr, 0, sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = inet_addr(argv[1]); servAddr.sin_port = htons(atoi(argv[2])); if (connect(hSocket, (struct sockaddr*) & servAddr, sizeof(servAddr)) == SOCKET_ERROR) ErrorHandling("connect error!"); else printf("connected... ...\n"); while (1) { fputs("Input message(Q to quit):", stdout); fgets(message, BUF_SIZE, stdin); if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) break; send(hSocket, message, BUF_SIZE, 0); strLen = recv(hSocket, message, BUF_SIZE, 0); printf("Message from server: %s\n", message); } closesocket(hSocket); WSACleanup(); system("PAUSE"); return 0; } void ErrorHandling(const char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
unit 13 多种I/O函数
习题
1.下面关于MSG_OOB可选项的说法错误的是?
a,b,c
2.利用readv&writev函数收发数据有何优点?分别从函数调用次数和I/O缓冲的角度给出说明
通过writev函数可以将分散保存在多个缓冲中的数据一并发送,通过readv函数可以由多个缓冲分别接受,这两个函数可以减少I/O函数的调用次数。
3.通过recv函数见证输入缓冲是否存在数据时(确认后立即返回),如何设置recv函数最后一个参数中的可选项?分别说明各可选项的含义
MSG_PEEK|MSG_DONTWAIT
MSG_PEEK:验证输入缓冲中是否存在接收的数据
MSG_DONTWAIT:调用I/O函数时不阻塞,用于使用非阻塞I/O。
4.可在Linux平台通过注册时间处理函数接收MSG_OOB数据。那Windows中如何接受?请说明接收方法
通过重叠I/O实现
unit 14 多播与广播
多播
sender
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <WS2tcpip.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define TTL 64 #define BUF_SIZE 30 void error_handling(char* message); int main(int argc, char* argv[]) { WSADATA wsaData; int send_sock; struct sockaddr_in mul_adr; int time_live = TTL; FILE* fp; char buf[BUF_SIZE]; if (argc != 3) { printf("Usage:%s <IP> <PORT>\n", argv[0]); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) error_handling("WSAStartup() Error!"); send_sock = socket(PF_INET, SOCK_DGRAM, 0); memset(&mul_adr, 0, sizeof(mul_adr)); mul_adr.sin_family = AF_INET; mul_adr.sin_addr.s_addr = inet_addr(argv[1]); mul_adr.sin_port = htons(atoi(argv[2])); // 设置TTL相关协议层为IPPROTO,选项名为IP_MULTICAST_TTL setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live)); // 打开文件,流指针fp if ((fp = fopen("news.txt", "r")) == NULL) error_handling("fopen() error!"); // 循环读取到文件末尾,每次读取BUF_SIZE,并发送出去 while (1) { void* s = fgets(buf, BUF_SIZE, fp); if (s == NULL) break; sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&mul_adr, sizeof(mul_adr)); //memset(buf, 0, BUF_SIZE); puts("acc!"); Sleep(2000); } fclose(fp); closesocket(send_sock); return 0; } void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
receiver
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <WS2tcpip.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SIZE 30 void error_handling(char* message); int main(int argc, char* argv[]) { WSADATA wsaData; int recv_sock; int str_len; char buf[BUF_SIZE]; struct ip_mreq join_adr; struct sockaddr_in recv_adr; if (argc != 3) { printf("Usage: %s<GroupIP><PORT>\n", argv[0]); exit(1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) error_handling("WSAStartup() Error!"); recv_sock = socket(PF_INET, SOCK_DGRAM, 0); memset(&recv_adr, 0, sizeof(recv_adr)); recv_adr.sin_family = AF_INET; recv_adr.sin_addr.s_addr = htonl(INADDR_ANY); recv_adr.sin_port = htons(atoi(argv[2])); // 端口复用 int opt = 1; setsockopt(recv_sock, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt)); if (bind(recv_sock, (struct sockaddr*)&recv_adr, sizeof(recv_adr)) == SOCKET_ERROR) error_handling("bind() error!"); join_adr.imr_multiaddr.s_addr = inet_addr(argv[1]); join_adr.imr_interface.s_addr = htonl(INADDR_ANY); setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr)); while (1) { str_len = recvfrom(recv_sock, buf, BUF_SIZE - 1, 0, NULL, 0); if (str_len == 0) break; buf[str_len] = '\0'; fputs(buf, stdout); } closesocket(recv_sock); return 0; } void error_handling(char* message) { fputs(message, stderr); fputc('\n', stderr); system("PAUSE"); exit(1); }
广播
sender
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 30 void error_handling(char* message); int main(int argc, char* argv[]) { int send_sock; struct sockaddr_in broad_adr; FILE *fp; char buf[BUF_SIZE]; int so_brd = 1; if(argc != 3){ printf("Usage:%s <IP> <PORT>\n", argv[0]); exit(1); } send_sock = socket(PF_INET, SOCK_DGRAM, 0); memset(&broad_adr, 0, sizeof(broad_adr)); broad_adr.sin_family = AF_INET; broad_adr.sin_addr.s_addr = inet_addr(argv[1]); broad_adr.sin_port = htons(atoi(argv[2])); setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&so_brd, sizeof(so_brd)); if((fp=fopen("news.txt","r")) == NULL) error_handling("fopen() error!"); while(1){ void* s = fgets(buf, BUF_SIZE, fp); if(s == NULL) break; sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&broad_adr, sizeof(broad_adr)); sleep(2); } close(send_sock); return 0; } void error_handling(char* message){ fputs(message, stderr); fputc('\n', stderr); exit(1); }
receiver
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 30 void error_handling(char* message); int main(int argc, char* argv[]) { int recv_sock; struct sockaddr_in recv_adr; char buf[BUF_SIZE]; int str_len; if(argc != 2){ printf("Usage:%s <PORT>", argv[0]); exit(1); } recv_sock = socket(PF_INET, SOCK_DGRAM, 0); memset(&recv_adr, 0, sizeof(recv_adr)); recv_adr.sin_family = AF_INET; recv_adr.sin_addr.s_addr = htonl(INADDR_ANY); recv_adr.sin_port = htons(atoi(argv[1])); int opt = 1; setsockopt( recv_sock, SOL_SOCKET,SO_REUSEADDR, (void *)&opt, sizeof(opt) ); if(bind(recv_sock, (struct sockaddr*)&recv_adr, sizeof(recv_adr)) == -1) error_handling("bind() error!"); while(1){ int len = sizeof(recv_adr); str_len = recvfrom(recv_sock, buf, BUF_SIZE-1, 0, (struct sockaddr*)&recv_adr, &len); if(str_len < 0) break; buf[str_len] = '\0'; fputs(buf, stdout); } close(recv_sock); return 0; } void error_handling(char* message){ fputs(message, stderr); fputc('\n', stderr); exit(1); }
课后练习
1.TTL的含义是什么?请从路由器的角度说明较大的TTL值与较小的TTL值之间的区别及问题
TTL是生存时间,即数据包传输的生存时间,是决定数据包传输距离的主要因素。
2.多播和广播的异同是什么?请从数据通信的角度进行说明
同:一次向多个主机发送数据
异:前者跨网络传输,后者同一网络。
3.
b,c
4.多播也对网络流量有利,请比较TCP数据交换方式解释其原因。
TCP传输有多少需要发送主机就有多少套接字,且传输过程中存在同一区域发送多次相同数据包,多播只需要发送一次。
5.多播方式的数据通信需要MBone虚拟网络。换言之,MBone是用于多播的网络,但它是虚拟网络。请解释此处“虚拟网络”。
通过网络中的特殊协议工作的软件概念上的网络。
unit 15 套接字和标准I/O
1.请说明标准I/O函数的2个优点。它为何拥有这2个优点?
良好的移植性,利用缓存提高性能。
因为这些函数都是按照ANSI C标准定义
2.标准IO中,“调用fputs函数传输数据时,调用后应立即开始发送!”,为何这种想法是错误的?为了达到这种效果应添加哪些处理过程?
调用调用fputs函数,首先将数据发送到I/O输出缓冲中,利用fflush再发送到套接字输出缓冲中,最后发送到网络。
unit 16 关于I/O流分离的其他内容
习题
1.
a,b
2.
c
unit 17 优于select的epoll
1.利用select函数实现服务器端时,代码层面存在的2个缺点是?
调用select函数后常见的针对所有文件描述符的循环语句
每次调用select函数时都需要向该函数传递监视对象信息
2.无论是select方式还是epoll方式,都需要将监视对象文件描述符信息通过函数调用传递给操作系统。请解释传递该信息的原因
因为套接字都是由操作系统管理的
3.select方式和epoll方式的最大差异在于监视对象文件描述符传递给操作系统的方式。请说明具体的差异,并解释为何存在这种差异。
实现的机制不同,无需每次循环传递监视信息。
4.虽然epoll是select的改进方式,但select也有自己的缺点。在何种情况下使用select方式更合理
服务器端接入者少
程序应具有兼容性
unit 18 多线程服务器端的实现
局域网聊天
服务端
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <string.h> #include <arpa/inet.h> #include <sys/socket.h> #define BUF_SIZE 100 #define MAX_CLNT 256 void* handle_clnt(void* arg); void send_msg(char* msg, int len); void error_handling(char* msg); int clnt_cnt = 0; int clnt_socks[MAX_CLNT]; pthread_mutex_t mutex; int main(int argc, char* argv[]) { int serv_sock, clnt_sock; struct sockaddr_in serv_adr, clnt_adr; int clnt_adr_sz; pthread_t t_id; if(argc != 2){ printf("Usage:%s<port>\n", argv[0]); exit(1); } pthread_mutex_init(&mutex, NULL); serv_sock = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); serv_adr.sin_port = htons(atoi(argv[1])); if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) error_handling("bind() error"); if(listen(serv_sock, 5) == -1) error_handling("listen() error"); while(1){ clnt_adr_sz = sizeof(clnt_adr); clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz); pthread_mutex_lock(&mutex); clnt_socks[clnt_cnt++] = clnt_sock; pthread_mutex_unlock(&mutex); pthread_create(&t_id, NULL, handle_clnt, (void*)&clnt_sock); pthread_detach(t_id); printf("Connect client IP: %s\n", inet_ntoa(clnt_adr.sin_addr)); } close(serv_sock); pthread_mutex_destroy(&mutex); return 0; } void* handle_clnt(void* arg){ int clnt_sock = *((int*)arg); int i, str_len = 0; char message[BUF_SIZE]; while((str_len = read(clnt_sock, message, sizeof(message))) != 0) send_msg(message, str_len); pthread_mutex_lock(&mutex); for(i = 0; i < clnt_cnt; ++i){ if(clnt_sock == clnt_socks[i]){ while(i < clnt_cnt - 1){//? clnt_socks[i] = clnt_socks[i+1]; ++i; } break; } } clnt_cnt--; pthread_mutex_unlock(&mutex); close(clnt_sock); return NULL; } void send_msg(char* msg, int len){ int i; pthread_mutex_lock(&mutex); for(i = 0; i <clnt_cnt; ++i) write(clnt_socks[i], msg, len); pthread_mutex_unlock(&mutex); } void error_handling(char *msg){ fputs(msg, stderr); fputc('\n', stderr); exit(1); }
客户端
#include <stdio.h> #include <stdlib.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <sys/socket.h> #include <pthread.h> #define BUF_SIZE 100 #define NAME_SIZE 20 void* send_msg(void* msg); void* recv_msg(void* msg); void* error_handling(char* msg); char name[NAME_SIZE] = "[DEFAULT]"; char msg[BUF_SIZE]; int main(int argc, char* argv[]) { int sock; struct sockaddr_in serv_adr; pthread_t snd_thread, rcv_thread; void* thread_return; if(argc != 4){ printf("Usage:%s <IP> <port> <name>\n", argv[0]); exit(1); } sprintf(name, "[%s]", argv[3]); sock = socket(PF_INET, SOCK_STREAM, 0); memset(&serv_adr, 0, sizeof(serv_adr)); serv_adr.sin_family = AF_INET; serv_adr.sin_addr.s_addr = inet_addr(argv[1]); serv_adr.sin_port = htons(atoi(argv[2])); if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) error_handling("connect() error"); pthread_create(&snd_thread, NULL, send_msg, (void*)&sock); pthread_create(&rcv_thread, NULL, recv_msg, (void*)&sock); pthread_join(snd_thread, &thread_return); pthread_join(rcv_thread, &thread_return); close(sock); return 0; } void* send_msg(void* msg){ int sock = *((int*)msg); char name_msg[NAME_SIZE+BUF_SIZE]; while(1){ fgets(msg, BUF_SIZE, stdin); if(!strcmp(msg, "q\n") || !strcmp(msg, "Q\n")){ close(sock); exit(0); } sprintf(name_msg, "%s %s", name, msg); write(sock, name_msg, strlen(name_msg)); } return NULL; } void* recv_msg(void* msg){ int sock = *((int*)msg); char name_msg[BUF_SIZE+NAME_SIZE]; int str_len; while(1){ str_len = read(sock, name_msg, BUF_SIZE+NAME_SIZE-1); if(str_len == -1) return (void*)-1; name_msg[str_len] = '\0'; fputs(name_msg, stdout); } return NULL; } void* error_handling(char* msg){ fputs(msg, stderr); fputc('\n', stderr); exit(1); }
习题
1.单CPU系统中如何同时执行多个进程?请解释该过程中发生的上下文切换
系统将CPU时间分成多个微小的块后分配给了多个进程。
运行程序前需要将相应进程信息读入内存,如果运行进程A后需要紧接着运行进程B,就应该将进程A相关信息移出内存,并读入进程B相关信息。
2.为何线程的上下文切换速度相对更快?线程间数据交换为何不需要类似IPC的特别技术?
每个进程都有独立的栈区,堆区,数据区,但是相乘共享堆区,数据区,在上下文切换时速度更快。
因为同时可以利用数据区和堆区交换数据。
3.请从执行流角度说明进程和线程的区别
进程:在操作系统构成单独执行流的单位
线程:在进程构成单独执行流的单位
4.
b,c
5.
d
6.
pthread_join
pthread_detach
unit 19 Windows平台下线程的使用
临界区(Critical section)与互斥体(Mutex)的区别
1、临界区只能用于对象在同一进程里线程间的互斥访问;互斥体可以用于对象进程间或线程间的互斥访问。
2、临界区是非内核对象,只在用户态进行锁操作,速度快;互斥体是内核对象,在核心态进行锁操作,速度慢。
3、临界区和互斥体在Windows平台都下可用;Linux下只有互斥体可用。
临界区,互斥量,信号量,事件的区别
四种进程或线程同步互斥的控制方法
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
2、互斥量:为协调共同对一个共享资源的单独访问而设计的。
3、信号量:为控制一个具有有限数量用户资源而设计。
4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
线程同步代码(临界区和互斥量)
#include <stdio.h> #include <Windows.h> #include <stdlib.h> #include <process.h> #define THREAD_NUM 50 #define ll long long unsigned WINAPI Thread_inc(void* arg); unsigned WINAPI Thread_des(void* arg); ll num = 0; HANDLE hMutex; // a. 临界区 // a. CRITICAL_SECTION g_cs; int main(int argc, char *argv[]) { HANDLE hThreads[THREAD_NUM]; DWORD wr; unsigned int ThreadID; int i; printf("sizeof long long: %d\n", sizeof(long long)); // b. 创建互斥量 hMutex = CreateMutex(NULL, FALSE, NULL); // a. InitializeCriticalSection(&g_cs); // 1. 创建句柄, 奇数减,偶数增 for (i = 0; i < THREAD_NUM; ++i) { if (i % 2) hThreads[i] = (HANDLE)_beginthreadex(NULL, 0, Thread_des, NULL, 0, &ThreadID); else hThreads[i] = (HANDLE)_beginthreadex(NULL, 0, Thread_inc, NULL, 0, &ThreadID); } if ((wr = WaitForMultipleObjects(THREAD_NUM, hThreads, TRUE, INFINITE)) == WAIT_FAILED) { puts("WaitForMultipleBojects() error"); exit(1); } printf("Result: %lld\n", num); puts("end of main"); // a. DeleteCriticalSection(&g_cs); system("PAUSE"); return 0; } unsigned WINAPI Thread_des(void* arg) { int i; WaitForSingleObject(hMutex, INFINITE); // a. EnterCriticalSection(&g_cs); for (i = 0; i < 50000000; ++i) { num--; } ReleaseMutex(hMutex); // a. LeaveCriticalSection(&g_cs); return 0; } unsigned WINAPI Thread_inc(void* arg) { int i; // a. EnterCriticalSection(&g_cs); WaitForSingleObject(hMutex, INFINITE); for (i = 0; i < 50000000; ++i) { num++; } ReleaseMutex(hMutex); // a. LeaveCriticalSection(&g_cs); return 0; }
课后练习
1.
bcd
2.
bd
3.请比较从内存中完全销毁Windows线程和Linux线程的方法
Windows上的线程销毁是随其线程main函数的返回,在内存中自动销毁的。但是linux的线程销毁必须经过pthread_join函数或者pthread_detach函数的响应才能在内存空间中完全销毁
4.通过线程创建过程解释内核对象、线程、句柄之间的关系
可以通过句柄区分内核对象,通过内核对象区分线程。
5.
x,x,x
6.请解释"auto-reset模式"和"manual-reset模式"的内核对象。区分二者的内核对象特征是什么?
使用WaitForSingleObject/WaitForMultipleObjects时,由于发生时间(变为signaled状态)返回时,有时候会把相应内核对象再次改为non-signaled状态,这种可以再次进入non-signaled状态的内核对象成为“auto-reset模式”的内核对象,而不会自动进入的内核对象成为“manual-reset模式”
unit 20 Windows中的线程同步
windows多线程同步聊天
服务端
#include <stdio.h> #include <stdlib.h> #include <Windows.h> // #include <WinSock2.h> #include <process.h> #include <string.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SZ 200 #define MAX_CLNT 256 void ErrorHandling(const char* msg); unsigned WINAPI HandleClnt(void* arg); void SendMsg(char* msg, int len); int clntCnt = 0; SOCKET clntSocks[MAX_CLNT]; HANDLE hMutex; int main(int argc, char* argv[]) { WSADATA wsaData; SOCKADDR_IN servAdr, clntAdr; SOCKET hServSock, hClntSock; int clntAdrSz; HANDLE hThread; if (argc != 2) { printf("Usage: %s <PORT>\n", argv[0]); system("PAUSE"); exit(-1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { ErrorHandling("WSAStartup() error"); } // 创建互斥量 hMutex = CreateMutex(NULL, FALSE, NULL); hServSock = socket(PF_INET, SOCK_STREAM, 0); if (hServSock == INVALID_SOCKET) ErrorHandling("socket() error"); memset(&servAdr, 0, sizeof(servAdr)); servAdr.sin_family = AF_INET; servAdr.sin_addr.s_addr = htonl(INADDR_ANY); servAdr.sin_port = htons(atoi(argv[1])); if (bind(hServSock, (struct sockaddr*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR) ErrorHandling("bind() error"); if (listen(hServSock, 5) == SOCKET_ERROR) ErrorHandling("listen() error"); while (1) { // 接受客户端连接请求 clntAdrSz = sizeof(clntAdr); hClntSock = accept(hServSock, (struct sockaddr*)&clntAdr, &clntAdrSz); // 存储套接字:多线程,互斥量使得线程同步 WaitForSingleObject(hMutex, INFINITE); clntSocks[clntCnt++] = hClntSock; ReleaseMutex(hMutex); // 创建线程 hThread = (HANDLE)_beginthreadex(NULL, 0, HandleClnt, (void*)&hClntSock, 0, NULL); printf("Connect client IP: %s\n", inet_ntoa(servAdr.sin_addr)); } closesocket(hServSock); WSACleanup(); system("PAUSE"); return 0; } void ErrorHandling(const char* msg) { fputs(msg, stderr); fputc('\n', stderr); system("PAUSE"); exit(-1); } unsigned __stdcall HandleClnt(void* arg) { // 获取客户端套接字参数 SOCKET hClntSock = *((SOCKET*)arg); int i, strLen = 0; char msg[BUF_SZ] = { 0 }; // 接受数据 while ((strLen = recv(hClntSock, msg, BUF_SZ, 0)) != 0) SendMsg(msg, strLen); // 清除套接字:将数组后面套接字前移 WaitForSingleObject(hMutex, INFINITE); for (i = 0; i < clntCnt; ++i) { if (hClntSock == clntSocks[i]) { while (i < clntCnt - 1){ clntSocks[i] = clntSocks[i + 1]; i++; } break; } } clntCnt--; ReleaseMutex(hMutex); closesocket(hClntSock); return 0; } void SendMsg(char* msg, int len) { int i; // 读取套接字:发送信息 WaitForSingleObject(hMutex, INFINITE); for (i = 0; i < clntCnt; ++i) { send(clntSocks[i], msg, len, 0); } ReleaseMutex(hMutex); }
客户端
#include <stdio.h> #include <stdlib.h> #include <process.h> #include <Windows.h> #include <string.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SZ 100 #define NAME_SZ 20 void ErrorHandling(const char* msg); unsigned WINAPI SendMsg(void* arg); unsigned WINAPI RecvMsg(void* arg); char name[NAME_SZ] = "[DEFAULT]"; char msg[BUF_SZ] = ""; int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hSock; SOCKADDR_IN servAdr; HANDLE hSndThread, hRcvThread; if (argc != 4) { printf("Usage: %s <IP> <port> <name>\n", argv[0]); system("PAUSE"); exit(-1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error"); // 初始化用户名 sprintf(name, "[%s]", argv[3]); // 初始化结构体 if ((hSock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) ErrorHandling("socket() error"); memset(&servAdr, 0, sizeof(servAdr)); servAdr.sin_family = AF_INET; servAdr.sin_addr.s_addr = inet_addr(argv[1]); servAdr.sin_port = htons(atoi(argv[2])); if (connect(hSock, (struct sockaddr*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR) ErrorHandling("connect() error"); // 创建线程 hSndThread = (HANDLE)_beginthreadex(NULL, 0, SendMsg, (void*)&hSock, 0, NULL); hRcvThread = (HANDLE)_beginthreadex(NULL, 0, RecvMsg, (void*)&hSock, 0, NULL); // 检测线程信号量 WaitForSingleObject(hSndThread, INFINITE); WaitForSingleObject(hRcvThread, INFINITE); closesocket(hSock); WSACleanup(); system("PAUSE"); return 0; } void ErrorHandling(const char* msg) { fputs(msg, stderr); fputc('\n', stderr); system("PAUSE"); exit(-1); } unsigned __stdcall SendMsg(void* arg) { SOCKET hSock = *((SOCKET*)arg); char namemsg[BUF_SZ + NAME_SZ] = { 0 }; while (1) { fgets(msg, BUF_SZ, stdin); if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n")) { closesocket(hSock); exit(0); } sprintf(namemsg, "%s %s", name, msg); send(hSock, namemsg, strlen(namemsg), 0); } return 5; } unsigned __stdcall RecvMsg(void* arg) { SOCKET hSock = *((SOCKET*)arg); int strLen = 0; char namemsg[BUF_SZ + NAME_SZ] = { 0 }; while (1) { if ((strLen = recv(hSock, namemsg, NAME_SZ + BUF_SZ + 1, 0)) == -1) exit(-1); namemsg[strLen] = '\0'; fputs(namemsg, stdout); } return 6; }
多线程同步
临界区
#include <stdio.h> #include <stdlib.h> #include <process.h> #include <Windows.h> #define THREAD_NUM 50 #define ll long long //unsigned WINAPI thread_main(void* arg); unsigned WINAPI thread_inc(void* arg); unsigned WINAPI thread_des(void* arg); ll num = 0; CRITICAL_SECTION g_cs; int main(int argc, char* argv[]) { //HANDLE hThread; //unsigned int ThreadID; //int param = 5; //DWORD wr; //hThread = (HANDLE)_beginthreadex(NULL, 0, thread_main, (void*)¶m, 0, &ThreadID); //if (hThread == 0) { // puts("_beginthreadex() error!"); // exit(-1); //} //if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED) { // puts("WaitForSingleObject() error!"); // exit(-1); //} //puts("end main thread"); //system("PAUSE"); //return 0; HANDLE hThread[THREAD_NUM]; DWORD wr; int i; // 初始化临界区 InitializeCriticalSection(&g_cs); for (i = 0; i < THREAD_NUM; ++i) { if (i % 2) hThread[i] = (HANDLE)_beginthreadex(NULL, 0, thread_inc, NULL, 0, NULL); else hThread[i] = (HANDLE)_beginthreadex(NULL, 0, thread_des, NULL, 0, NULL); } if ((wr = WaitForMultipleObjects(THREAD_NUM, hThread, TRUE, INFINITE)) == WAIT_FAILED) { puts("WaitForMultipleObjects() error"); exit(-1); } DeleteCriticalSection(&g_cs); printf("Result: num = %lld\n", num); puts("end thread main"); Sleep(5000); return 0; } //unsigned WINAPI thread_main(void* arg) { // int i; // int cnt = *((int*)arg); //;. // for (i = 0; i < cnt; ++i) { // printf("Running thread%d\n", i + 1); // } // // return 5; //} unsigned WINAPI thread_inc(void* arg) { int i; EnterCriticalSection(&g_cs); for (i = 0; i < 5000000; ++i) { num++; } LeaveCriticalSection(&g_cs); return 5; } unsigned WINAPI thread_des(void* arg) { int i; EnterCriticalSection(&g_cs); for (i = 0; i < 5000000; ++i) { num--; } LeaveCriticalSection(&g_cs); return 6; }
互斥量
#include <stdio.h> #include <stdlib.h> #include <Windows.h> #include <process.h> #define THREAD_NUM 50 #define ll long long unsigned WINAPI thread_inc(void* arg); unsigned WINAPI thread_des(void* arg); ll num = 0; HANDLE hMutex; int main(int argc, char* argv[]) { HANDLE hThread[THREAD_NUM]; DWORD wr; int i; // 创建互斥对象 hMutex = CreateMutex(NULL, FALSE, NULL); for (i = 0; i < THREAD_NUM; ++i) { if (i % 2) hThread[i] = (HANDLE)_beginthreadex(NULL, 0, thread_inc, NULL, 0, NULL); else hThread[i] = (HANDLE)_beginthreadex(NULL, 0, thread_des, NULL, 0, NULL); } if ((wr = WaitForMultipleObjects(THREAD_NUM, hThread, TRUE, INFINITE)) == WAIT_FAILED) { puts("WaitForMultipleObjects() error"); exit(1); } CloseHandle(hMutex); printf("Result: %lld\n", num); system("PAUSE"); return 0; } unsigned WINAPI thread_inc(void* arg) { int i; WaitForSingleObject(hMutex, INFINITE); for (i = 0; i < 50000000; ++i) { num++; } ReleaseMutex(hMutex); return 5; } unsigned WINAPI thread_des(void* arg) { int i; WaitForSingleObject(hMutex, INFINITE); for (i = 0; i < 50000000; ++i) { num--; } ReleaseMutex(hMutex); return 6; }
信号量
#include <stdio.h> #include <stdlib.h> #include <process.h> #include <Windows.h> #define THREAD_NUM 50 #define ll long long unsigned WINAPI thread_inc(void* arg); unsigned WINAPI thread_des(void* arg); ll num = 0; // CRITICAL_SECTION g_cs; // HANDLE hMutex; HANDLE hSem1, hSem2; int main(int argc, char* argv[]) { HANDLE hThread[THREAD_NUM]; DWORD wr; int i; // 1. 临界区 // InitializeCriticalSection(&g_cs); // 2. 互斥量 // hMutex = CreateMutex(NULL, FALSE, NULL); // 3. 信号量 hSem1 = CreateSemaphore(NULL, 1, 1, NULL); hSem2 = CreateSemaphore(NULL, 0, 1, NULL); for (i = 0; i < THREAD_NUM; ++i) { if (i % 2) hThread[i] = (HANDLE)_beginthreadex(NULL, 0, thread_inc, (void*)&i, 0, NULL); else hThread[i] = (HANDLE)_beginthreadex(NULL, 0, thread_des, (void*)&i, 0, NULL); } if ((wr = WaitForMultipleObjects(THREAD_NUM, hThread, TRUE, INFINITE)) == WAIT_FAILED) { puts("WaitForMultipleObjects() error"); exit(1); } // DeleteCriticalSection(&g_cs); // CloseHandle(hMutex); CloseHandle(hSem1); CloseHandle(hSem2); printf("Result:%lld\n", num); puts("end main thread"); system("PAUSE"); return 0; } unsigned __stdcall thread_inc(void* arg) { int i; // EnterCriticalSection(&g_cs); // WaitForSingleObject(hMutex, INFINITE); int n = *((int*)arg); WaitForSingleObject(hSem1, INFINITE); for (i = 0; i < 50000000; ++i) num++; // LeaveCriticalSection(&g_cs); // ReleaseMutex(hMutex); ReleaseSemaphore(hSem2, 1, NULL); return 5; } unsigned __stdcall thread_des(void* arg) { int i; // EnterCriticalSection(&g_cs); // WaitForSingleObject(hMutex, INFINITE); WaitForSingleObject(hSem2, INFINITE); for (i = 0; i < 50000000; ++i) num--; // LeaveCriticalSection(&g_cs); // ReleaseMutex(hMutex); ReleaseSemaphore(hSem1, 1, NULL); return 6; }
事件
#include <stdio.h> #include <stdlib.h> #include <Windows.h> #include <process.h> #define THREAD_NUM 50 #define ll long long unsigned WINAPI thread_inc(void* arg); unsigned WINAPI thread_des(void* arg); ll num = 0; HANDLE hEvent1, hEvent2; int main(int argc, char* argv[]) { HANDLE hThread1, hThread2; hEvent1 = CreateEvent(NULL, TRUE, TRUE, NULL); hEvent2 = CreateEvent(NULL, TRUE, FALSE, NULL); hThread1 = (HANDLE)_beginthreadex(NULL, 0, thread_inc, NULL, 0, NULL); hThread2 = (HANDLE)_beginthreadex(NULL, 0, thread_des, NULL, 0, NULL); //puts("wait for begining"); //SetEvent(hEvent); WaitForSingleObject(hThread1, INFINITE); WaitForSingleObject(hThread2, INFINITE); ResetEvent(hEvent1); ResetEvent(hEvent2); printf("Result=%lld\n", num); puts("end main thread"); system("PAUSE"); return 0; } unsigned __stdcall thread_inc(void* arg) { int i; WaitForSingleObject(hEvent1, INFINITE); for (i = 0; i < 50000000; ++i) num++; SetEvent(hEvent2); return 5; } unsigned __stdcall thread_des(void* arg) { int i; WaitForSingleObject(hEvent2, INFINITE); for (i = 0; i < 50000000; ++i) num--; SetEvent(hEvent1); return 6; }
课后练习
1.
c
2.
√,√,×,×
3.本章示例SyncSema_win.c的Read函数中,退出临界区需要较长时间,请给出解决方案并实现
使用scanf可能造成程序阻塞,所以应该将scanf输入的值,转存入num,转存过程写入临界区
unit 21 异步通知I/O模型
异步通知I/O回声服务器
AsyncNotiEchoServ_win.c
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SZ 100 void CompressSockets(SOCKET hSockArr[], int idx, int totle); void CompressEvents(WSAEVENT hEventArr[], int idx, int totle); void ErrorHandling(const char* msg); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hServSock, hClntSock; SOCKADDR_IN servAdr, clntAdr; // 存储套接字句柄 SOCKET hSockArr[WSA_MAXIMUM_WAIT_EVENTS]; // 存储套接字相关事件句柄 WSAEVENT hEventArr[WSA_MAXIMUM_WAIT_EVENTS]; WSAEVENT newEvent; // 区分事件类型,创建对象 WSANETWORKEVENTS netEvents; // 客户端套接字数量 int numOfClntSock = 0; int strLen, i; // 循环,判断是否所有事件对象变为signaled int posInfo, startIdx; int clntAdrLen; char msg[BUF_SZ] = { 0 }; // 建立服务器连接 if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); exit(-1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error"); hServSock = socket(PF_INET, SOCK_STREAM, 0); memset(&servAdr, 0, sizeof(servAdr)); servAdr.sin_family = AF_INET; servAdr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); servAdr.sin_port = htons(atoi(argv[1])); if (bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR) ErrorHandling("bind() error"); if (listen(hServSock, 5) == SOCKET_ERROR) ErrorHandling("listen() error"); // 创建事件对象 newEvent = WSACreateEvent(); /* 实现异步通知I/O模型,接受客户端连接请求 只要套接字发生FD_ACCEPT事件,则设置netEvent对象为signaled 无论事件发生与否,调用后都会返回 */ if (WSAEventSelect(hServSock, newEvent, FD_ACCEPT) == SOCKET_ERROR) ErrorHandling("WSAEventSelect() error"); // 存储套接字状态,以及发生套接字的相关事件 hSockArr[numOfClntSock] = hServSock; hEventArr[numOfClntSock] = newEvent; numOfClntSock++; while (1) { /* 通过验证的第一个为signaled的对象,返回对应位置信息(posInfo-WSA_WAIT_EVENT_0) 因为对象信息是顺序存储,循环判断发生变化的事件对象句柄的信息 */ posInfo = WSAWaitForMultipleEvents(numOfClntSock, hEventArr, FALSE, WSA_INFINITE, FALSE); startIdx = posInfo - WSA_WAIT_EVENT_0; for (i = startIdx; i < numOfClntSock; ++i) { // 循环所有事件句柄,找到发生变化(值为signaled)的句柄位置 int sigEventIdx = WSAWaitForMultipleEvents(1, &hEventArr[i], TRUE, 0, FALSE); // 函数失败或者超时 if (sigEventIdx == WSA_WAIT_FAILED || sigEventIdx == WSA_WAIT_TIMEOUT) { continue; } else { sigEventIdx = i; // 区分事件类型 WSAEnumNetworkEvents(hSockArr[sigEventIdx], hEventArr[sigEventIdx], &netEvents); // 请求连接时 if (netEvents.lNetworkEvents & FD_ACCEPT) { // 发生连接错误 if (netEvents.iErrorCode[FD_ACCEPT_BIT] != 0) { puts("Accept error"); break; } // 接受连接请求 clntAdrLen = sizeof(clntAdr); hClntSock = accept(hSockArr[sigEventIdx], (struct sockaddr*)&clntAdr, &clntAdrLen); // 判断客户端事件是否有数据发送或者断开连接的事件 newEvent = WSACreateEvent(); WSAEventSelect(hClntSock, newEvent, FD_READ | FD_CLOSE); hEventArr[numOfClntSock] = newEvent; hSockArr[numOfClntSock] = hClntSock; numOfClntSock++; puts("Connected new client..."); } // 客户端有数据发送 if (netEvents.lNetworkEvents & FD_READ) { if (netEvents.iErrorCode[FD_READ_BIT] != 0) { puts("Read error"); break; } // 接受数据,并发送回去 strLen = recv(hSockArr[sigEventIdx], msg, sizeof(msg), 0); send(hSockArr[sigEventIdx], msg, strLen, 0); } // 断开连接 if (netEvents.lNetworkEvents & FD_CLOSE) { if (netEvents.iErrorCode[FD_CLOSE_BIT] != 0) { puts("Close error"); break; } WSACloseEvent(hEventArr[sigEventIdx]); closesocket(hSockArr[sigEventIdx]); // 从套接字数组和对应的事件数组中,去除断开连接的对象 numOfClntSock--; CompressSockets(hSockArr, sigEventIdx, numOfClntSock); CompressEvents(hEventArr, sigEventIdx, numOfClntSock); } } } } WSACleanup(); system("PAUSE"); return 0; } void CompressSockets(SOCKET hSockArr[], int idx, int totle) { int i; for (i = idx; i < totle; ++i) { hSockArr[i] = hSockArr[i + 1]; } } void CompressEvents(WSAEVENT hEventArr[], int idx, int totle) { int i; for (i = 0; i < totle; ++i) { hEventArr[i] = hEventArr[i + 1]; } } void ErrorHandling(const char* msg) { fputs(msg, stderr); fputc('\n', stderr); system("PAUSE"); exit(-1); }
客户端和回声客户端(echo_client_win)相同
课后练习
1.结合send & recv 函数解释同步和异步方式的I/O。并请说明同步I/O的缺点,以及怎样通过异步I/O进行解决
同步:send(),recv()的调用和返回,与数据传输(接收),完成传输(接收)同步进行
异步:send(),recv()的调用和返回,与数据传输(接收),完成传输(接收)不同步进行
缺点:进行I/O过程中函数无法返回,所以不能执行其他任务
我们通过监控套接字I/O变化,和发生套接字I/O相关事件,将I/O时间的发生和处理分离。
2.异步I/O并不是所有状况下的最佳选择。它具有哪些缺点?何种情况下同步I/O更优?可以参考异步I/O相关源代码,亦可结合线程进行说明
异步I/O每次都需要监视和验证套接字及其事件,在少量连接和数据处理中,响应时间会比同步I/O慢,对于只创建了单线程或少量线程可以考虑使用同步I/O
3.
√,√,√
4.请从源代码的角度说明select函数和WSAEventSelect函数再使用上的差异
int select(int nfds, fd_set* readfds, fd_set *writefds, fd_set *excepfds, const struct timeval* timeout)
int WSAEventSelect(SOCKET s, WSAEVENT hEventObject, long lNetworkEvents)
对于指定I/O监视对象和实际验证状态,select是一起的,WSAEventSelect是分离的
select可以一次验证多个对象,但是WSAEventSelect一次只能验证一个对象
5.第17章的epoll可以在条件触发和边缘触发这2中方式下工作。那种方式更适合异步I/O模型?为什么?请概括说明
边缘触发,因为当缓冲区有数据时,边缘触发只在开始注册事件,后面都不会发起注册通知,这对于异步通知中每次对数据的发送接收,只有当有新的注册对象时,才会发起注册消息。
6.Linux中的epoll同样属于异步I/O模型。请说明原因
对于指定I/O监视对象和实际验证状态,epoll也是分离的,可以在事件发生后在指定时间发生事件。
7.如何获取WSAWaitForMultipleEvents可以监视的最大句柄数?请编写代码读取该值
输出WSA_MAXIMUM_WAIT_EVENTS的值
int ncount = WSA_MAXIMUM_WAIT_EVENTS; cout << ncount;
8.为何异步通知I/O模型中的事件对象必须是manual-reset模式
如果是auto-reset模型,则WSAWaitForMultipleEvents一开始监视完事件句柄,相关变化内核对象(变为signal)会被自动设置为non-signaled,则在下面循环验证,是否发出指定对象的信号(即hEventArr[i]是否为signaled,不等待立即返回),会出现错误,肯定不会出现变化的事件句柄。
9.请在本章的通知I/O模型的基础上编写聊天服务器端。要求该服务器能够结合第20章的聊天客户端chat_clnt_win.c运行
改一下发送方式为套接字数组全部发送就行
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SZ 100 void CompressSockets(SOCKET hSockArr[], int idx, int totle); void CompressEvents(WSAEVENT hEventArr[], int idx, int totle); void ErrorHandling(const char* msg); void SendMsg(SOCKET hSockArr[], int numOfClntSock, char* msg, int len); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hServSock, hClntSock; SOCKADDR_IN servAdr, clntAdr; // 存储套接字句柄 SOCKET hSockArr[WSA_MAXIMUM_WAIT_EVENTS]; // 存储套接字相关事件句柄 WSAEVENT hEventArr[WSA_MAXIMUM_WAIT_EVENTS]; WSAEVENT newEvent; // 区分事件类型,创建对象 WSANETWORKEVENTS netEvents; // 客户端套接字数量 int numOfClntSock = 0; int strLen, i; // 循环,判断是否所有事件对象变为signaled int posInfo, startIdx; int clntAdrLen; char msg[BUF_SZ] = { 0 }; // 建立服务器连接 if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); exit(-1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error"); hServSock = socket(PF_INET, SOCK_STREAM, 0); memset(&servAdr, 0, sizeof(servAdr)); servAdr.sin_family = AF_INET; servAdr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); servAdr.sin_port = htons(atoi(argv[1])); if (bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR) ErrorHandling("bind() error"); if (listen(hServSock, 5) == SOCKET_ERROR) ErrorHandling("listen() error"); // 创建事件对象 newEvent = WSACreateEvent(); /* 实现异步通知I/O模型,接受客户端连接请求 只要套接字发生FD_ACCEPT事件,则设置netEvent对象为signaled 无论事件发生与否,调用后都会返回 */ if (WSAEventSelect(hServSock, newEvent, FD_ACCEPT) == SOCKET_ERROR) ErrorHandling("WSAEventSelect() error"); // 存储套接字状态,以及发生套接字的相关事件 hSockArr[numOfClntSock] = hServSock; hEventArr[numOfClntSock] = newEvent; numOfClntSock++; while (1) { /* 通过验证的第一个为signaled的对象,返回对应位置信息(posInfo-WSA_WAIT_EVENT_0) 因为对象信息是顺序存储,循环判断发生变化的事件对象句柄的信息 */ posInfo = WSAWaitForMultipleEvents(numOfClntSock, hEventArr, FALSE, WSA_INFINITE, FALSE); startIdx = posInfo - WSA_WAIT_EVENT_0; for (i = startIdx; i < numOfClntSock; ++i) { // 循环所有事件句柄,找到发生变化(值为signaled)的句柄位置 int sigEventIdx = WSAWaitForMultipleEvents(1, &hEventArr[i], TRUE, 0, FALSE); // 函数失败或者超时 if (sigEventIdx == WSA_WAIT_FAILED || sigEventIdx == WSA_WAIT_TIMEOUT) { continue; } else { sigEventIdx = i; // 区分事件类型 WSAEnumNetworkEvents(hSockArr[sigEventIdx], hEventArr[sigEventIdx], &netEvents); // 请求连接时 if (netEvents.lNetworkEvents & FD_ACCEPT) { // 发生连接错误 if (netEvents.iErrorCode[FD_ACCEPT_BIT] != 0) { puts("Accept error"); break; } // 接受连接请求 clntAdrLen = sizeof(clntAdr); hClntSock = accept(hSockArr[sigEventIdx], (struct sockaddr*)&clntAdr, &clntAdrLen); // 判断客户端事件是否有数据发送或者断开连接的事件 newEvent = WSACreateEvent(); WSAEventSelect(hClntSock, newEvent, FD_READ | FD_CLOSE); hEventArr[numOfClntSock] = newEvent; hSockArr[numOfClntSock] = hClntSock; numOfClntSock++; printf("Connect client IP: %s\n", inet_ntoa(servAdr.sin_addr)); } // 客户端有数据发送 if (netEvents.lNetworkEvents & FD_READ) { if (netEvents.iErrorCode[FD_READ_BIT] != 0) { puts("Read error"); break; } // 接受数据,并发送回去 strLen = recv(hSockArr[sigEventIdx], msg, sizeof(msg), 0); SendMsg(hSockArr, numOfClntSock, msg, strLen); } // 断开连接 if (netEvents.lNetworkEvents & FD_CLOSE) { if (netEvents.iErrorCode[FD_CLOSE_BIT] != 0) { puts("Close error"); break; } WSACloseEvent(hEventArr[sigEventIdx]); closesocket(hSockArr[sigEventIdx]); // 从套接字数组和对应的事件数组中,去除断开连接的对象 numOfClntSock--; CompressSockets(hSockArr, sigEventIdx, numOfClntSock); CompressEvents(hEventArr, sigEventIdx, numOfClntSock); } } } } WSACleanup(); system("PAUSE"); return 0; } void CompressSockets(SOCKET hSockArr[], int idx, int totle) { int i; for (i = idx; i < totle; ++i) { hSockArr[i] = hSockArr[i + 1]; } } void CompressEvents(WSAEVENT hEventArr[], int idx, int totle) { int i; for (i = 0; i < totle; ++i) { hEventArr[i] = hEventArr[i + 1]; } } void ErrorHandling(const char* msg) { fputs(msg, stderr); fputc('\n', stderr); system("PAUSE"); exit(-1); } void SendMsg(SOCKET hSockArr[], int numOfClntSock, char* msg, int len) { int i; // 读取套接字:发送信息 for (i = 0; i < numOfClntSock; ++i) { send(hSockArr[i], msg, len, 0); } }
unit 22 重叠I/O模型
重叠I/O的I/O完成确认
事件对象
OverlappedSend_win
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") void ErrorHandling(const char* msg); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hSocket; SOCKADDR_IN sendAdr; WSABUF dataBuf;//待传输数据 char msg[] = "Network is Computer."; DWORD sendBytes = 0;//实际发送了多少字节 WSAOVERLAPPED overlapped;//结构体对象,使用事件判断是否完成传输 WSAEVENT evObj; if (argc != 3) { printf("Usage:%s <IP> <port>\n", argv[0]); exit(-1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error"); // 创建重叠套接字 hSocket = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); memset(&sendAdr, 0, sizeof(sendAdr)); sendAdr.sin_family = AF_INET; sendAdr.sin_addr.S_un.S_addr = inet_addr(argv[1]); sendAdr.sin_port = htons(atoi(argv[2])); if (connect(hSocket, (struct sockaddr*)&sendAdr, sizeof(sendAdr)) == SOCKET_ERROR) ErrorHandling("connect() error"); // 创建事件 evObj = WSACreateEvent(); // 初始化结构对象 memset(&overlapped, 0, sizeof(overlapped)); overlapped.hEvent = evObj; // 初始化WSABUF结构体,赋值数据信息 dataBuf.len = strlen(msg) + 1; dataBuf.buf = msg; // 判断数据是否传输完成 /* 如果没有错误发生并且发送操作立即完成,则 WSASend返回零。 在这种情况下,一旦调用线程处于警报状态,就已经计划完成例程的调用。 否则,将返回SOCKET_ERROR的值,并且可以通过调用WSAGetLastError来检索特定的错误代码 。 错误代码 WSA_IO_PENDING表示重叠操作已成功启动,并将在以后指示完成。 任何其他错误代码表示重叠操作未成功启动,并且不会出现完成指示。 */ if (WSASend(hSocket, &dataBuf, 1, &sendBytes, 0, &overlapped, NULL) == SOCKET_ERROR) { // 当数据传输尚未完成,仍处于传输状态 if (WSAGetLastError() == WSA_IO_PENDING) { puts("Background data receive"); WSAWaitForMultipleEvents(1, &evObj, TRUE, WSA_INFINITE, FALSE); WSAGetOverlappedResult(hSocket, &overlapped, &sendBytes, FALSE, NULL); } else { ErrorHandling("WSASend() error"); } } printf("Send data size: %u\n", sendBytes); WSACloseEvent(evObj); closesocket(hSocket); WSACleanup(); system("PAUSE"); return 0; } void ErrorHandling(const char* msg) { fputs(msg, stderr); fputc('\n', stderr); system("PAUSE"); exit(-1); }
OverlappedRecv_win
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SZ 1024 void ErrorHandling(const char* msg); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hLisnSock, hRecvSock; SOCKADDR_IN lisnAdr, recvAdr; int recvAdrSz; WSABUF dataBuf; WSAEVENT evObj; WSAOVERLAPPED overlapped; char buf[BUF_SZ] = { 0 }; DWORD recvBytes = 0, flags = 0; if (argc != 2) { printf("Usage:%s <port>\n", argv[1]); exit(-1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error"); hLisnSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); memset(&lisnAdr, 0, sizeof(lisnAdr)); lisnAdr.sin_family = AF_INET; lisnAdr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); lisnAdr.sin_port = htons(atoi(argv[1])); if (bind(hLisnSock, (SOCKADDR*)&lisnAdr, sizeof(lisnAdr)) == SOCKET_ERROR) { ErrorHandling("bind() error"); } if (listen(hLisnSock, 5) == SOCKET_ERROR) ErrorHandling("listen() error"); recvAdrSz = sizeof(recvAdr); hRecvSock = accept(hLisnSock, (SOCKADDR*)&recvAdr, &recvAdrSz); evObj = WSACreateEvent(); memset(&overlapped, 0, sizeof(overlapped)); overlapped.hEvent = evObj; dataBuf.buf = buf; dataBuf.len = BUF_SZ; if (WSARecv(hRecvSock, &dataBuf, 1, &recvBytes, &flags, &overlapped, NULL) == SOCKET_ERROR) { if (WSAGetLastError() == WSA_IO_PENDING) { puts("Background data receive"); WSAWaitForMultipleEvents(1, &evObj, TRUE, WSA_INFINITE, FALSE); WSAGetOverlappedResult(hRecvSock, &overlapped, &recvBytes, FALSE, NULL); } else { ErrorHandling("WSARecv() error"); } } printf("Received message: %s", buf); WSACloseEvent(evObj); closesocket(hRecvSock); closesocket(hLisnSock); WSACleanup(); system("PAUSE"); return 0; } void ErrorHandling(const char* msg) { fputs(msg, stderr); fputc('\n', stderr); system("PAUSE"); exit(-1); }
使用Completion Routine函数
CmpRoutinesRecv_win
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SZ 1024 /** * @brief * @param 错误信息,正常结束写入0 * @param 实际收发字节数 * @param 参数lpOverlapped * @param I/O函数传入信息或者0 */ void CALLBACK CompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD); void ErrorHandling(const char* msg); WSABUF dataBuf; char msg[BUF_SZ]; DWORD recvBytes; int main(int argc, char** argv) { WSADATA wsaData; SOCKET hLisnSock, hRecvSock; SOCKADDR_IN lisnAdr, recvAdr; int idx, recvAdrSz; DWORD Flag = 0; WSAOVERLAPPED overlapped; WSAEVENT evObj; if (argc != 2) { printf("Usage: %s <port>\n", argv[0]); exit(-1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error"); hLisnSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); memset(&lisnAdr, 0, sizeof(lisnAdr)); lisnAdr.sin_family = AF_INET; lisnAdr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); lisnAdr.sin_port = htons(atoi(argv[1])); if (bind(hLisnSock, (SOCKADDR*)&lisnAdr, sizeof(lisnAdr)) == SOCKET_ERROR) ErrorHandling("bind() error"); if (listen(hLisnSock, 5) == SOCKET_ERROR) ErrorHandling("listen() error"); recvAdrSz = sizeof(recvAdr); hRecvSock = accept(hLisnSock, (SOCKADDR*)&recvAdr, &recvAdrSz); evObj = WSACreateEvent(); memset(&overlapped, 0, sizeof(overlapped)); overlapped.hEvent = evObj; dataBuf.buf = msg; dataBuf.len = BUF_SZ; if (WSARecv(hRecvSock, &dataBuf, 1, &recvBytes, &Flag, &overlapped, CompRoutine) == SOCKET_ERROR) { if (WSAGetLastError() == WSA_IO_PENDING) { puts("background data receive"); } } // 1. 发生信号通知时,返回时间位置 // 2 .使线程进入可警告的等待状态,当系统执行I/O完成例程时,将返回WSA_WAIT_IO_COMPLETION idx = WSAWaitForMultipleEvents(1, &evObj, FALSE, WSA_INFINITE, TRUE); if (idx == WAIT_IO_COMPLETION) puts("Overlapped I/O completed"); else ErrorHandling("WSARecv() error"); //WSAGetOverlappedResult(hRecvSock, &overlapped, &recvBytes, FALSE, NULL); WSACloseEvent(evObj); closesocket(hRecvSock); closesocket(hLisnSock); WSACleanup(); system("PAUSE"); return 0; } void CALLBACK CompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flags) { if (dwError != 0) { ErrorHandling("CompRoutine error"); } else { recvBytes = szRecvBytes; printf("Received message: %s\n", msg); } } void ErrorHandling(const char* msg) { fputs(msg, stderr); fputc('\n', stderr); system("PAUSE"); exit(-1); }
习题
1.异步通知I/O模型和重叠I/O模型在异步处理方面有哪些区别?
异步重叠I/O在异步的基础上,需要对I/O完成确认。
2.请分析非阻塞I/O、异步I/O、重叠I/O之间的关系
非阻塞I/O是进行异步I/O的必要条件,异步I/O是进行重叠I/O的必要条件
3.阅读下面代码,指出问题并给出解决方案
当accept错误,下面WSARecv也会发生错误
每个重叠操作使用自己的OVERLAPPED数据结构对象,可用于区别这些重叠操作,但是while循环中使用同一个OVERLAPPED结构。
4.请从源代码角度说明调用WSASend函数后如何验证进入Pending状态
如果没有错误并且发送操作立即完成,则返回0,否则返回SOCKET_ERROR,使用WSAGetLastError()检测错误是否为WSA_IO_PENDING,若是,表明数据传输尚未完成,仍处于传输状态。
5.线程的"alertable wait状态"的含义是什么?说出能使线程进入这种状态的两个函数
等待接受操作系统消息的线程状态
WaitForMultipleObjectsEx
WaitForSingleObjectEx
WSAWaitForMultipleEvents
SleepEx
套接字重叠 I/O 与阻塞/非阻塞模式:https://support.microsoft.com/zh-cn/help/181611/socket-overlapped-i-o-versus-blocking-nonblocking-mode
重叠I/O:https://zh.wikipedia.org/wiki/%E9%87%8D%E5%8F%A0I/O
unit 23 IOCP
纯重叠I/O回声服务器
服务端
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SZ 1024 void ErrorHandling(const char* msg); void CALLBACK ReadCompRoutine(DWORD, DWORD, LPWSAOVERLAPPED, DWORD); void CALLBACK WriteCompRoutine(DWORD, DWORD, LPOVERLAPPED, DWORD); typedef struct { SOCKET hClntSock; char buf[BUF_SZ]; WSABUF dataBuf; }PER_IO_DATA, *LPPER_IO_DATA; int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hLisnSock, hRecvSock; SOCKADDR_IN lisnAdr, recvAdr; LPWSAOVERLAPPED lpOvlp; DWORD recvBytes; LPPER_IO_DATA hbInfo; int recvAdrSz = 0; DWORD flagInfo = 0; u_long mode = 1; if (argc != 2) { printf("Usage:%s <port>\n", argv[0]); system("PAUSE"); exit(-1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { ErrorHandling("WSAStartup() error"); } // 创建重叠套接字 hLisnSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); // 更改套接字属性为非阻塞模式 /* int ioctlsocket( int s, long cmd, u_long * argp); s:一个标识套接口的描述字。 cmd:对套接口s的操作命令。 argp:指向cmd命令所带参数的指针。 */ ioctlsocket(hLisnSock, FIONBIO, &mode); memset(&lisnAdr, 0, sizeof(lisnAdr)); lisnAdr.sin_family = AF_INET; lisnAdr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); lisnAdr.sin_port = htons(atoi(argv[1])); if (bind(hLisnSock, (SOCKADDR*)&lisnAdr, sizeof(lisnAdr)) == SOCKET_ERROR) ErrorHandling("bind() error"); if (listen(hLisnSock, 5) == SOCKET_ERROR) ErrorHandling("listen() error"); recvAdrSz = sizeof(recvAdr); while (1) { // 调用completion callback函数 SleepEx(100, TRUE); hRecvSock = accept(hLisnSock, (SOCKADDR*)&recvAdr, &recvAdrSz); // 非阻塞模式下,accept如果无法立即完成,返回INVALID_SOCKET, 如果为非阻塞,使用WSAGetLastError返回WSAEORBLOCK if (hRecvSock == INVALID_SOCKET) { if (WSAGetLastError() == WSAEWOULDBLOCK) continue; else ErrorHandling("accept error"); } puts("Client connected... ..."); // 申请重叠I/O中需要使用的结构体内存空间,并初始化。 // 在循环中申请WSAOVERLAPPED,因为每个客户端都需要独立的WSAOVERLAPPED结构体变量 lpOvlp = (LPWSAOVERLAPPED)malloc(sizeof(WSAOVERLAPPED)); memset(lpOvlp, 0, sizeof(WSAOVERLAPPED)); // 动态分配PER_IO_DATA结构体空间 hbInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA)); hbInfo->hClntSock = (DWORD)hRecvSock; (hbInfo->dataBuf).buf = hbInfo->buf; (hbInfo->dataBuf).len = BUF_SZ; lpOvlp->hEvent = (WSAEVENT)hbInfo; WSARecv(hRecvSock, &(hbInfo->dataBuf), 1, &recvBytes, &flagInfo, lpOvlp, ReadCompRoutine); } closesocket(hRecvSock); closesocket(hLisnSock); WSACleanup(); system("PAUSE"); return 0; } void CALLBACK ReadCompRoutine(DWORD dwError, DWORD szRecvBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flag) { LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent); SOCKET hSock = hbInfo->hClntSock; LPWSABUF bufInfo = &(hbInfo->dataBuf); DWORD sendBytes; if (szRecvBytes == 0) { closesocket(hSock); free(lpOverlapped->hEvent); free(lpOverlapped); puts("Client disconnected... ..."); } else { bufInfo->len = szRecvBytes; WSASend(hSock, bufInfo, 1, &sendBytes, 0, lpOverlapped, WriteCompRoutine); } } void CALLBACK WriteCompRoutine(DWORD dwError, DWORD szSendBytes, LPWSAOVERLAPPED lpOverlapped, DWORD flag) { LPPER_IO_DATA hbInfo = (LPPER_IO_DATA)(lpOverlapped->hEvent); SOCKET hSock = hbInfo->hClntSock; LPWSABUF bufInfo = &(hbInfo->dataBuf); DWORD recvBytes; DWORD flagInfo = 0; WSARecv(hSock, bufInfo, 1, &recvBytes, &flagInfo, lpOverlapped, ReadCompRoutine); } void ErrorHandling(const char* msg) { fputs(msg, stderr); fputc('\n', stderr); system("PAUSE"); exit(-1); }
客户端
#include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SZ 1024 void ErrorHandling(const char* msg); int main(int argc, char** argv) { WSADATA wsaData; SOCKET hSock; SOCKADDR_IN sockAdr; char msg[BUF_SZ] = { 0 }; int strLen, recvLen; if (argc != 3) { printf("Usage: %s <IP> <port>\n", argv[0]); exit(-1); } if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error"); hSock = socket(PF_INET, SOCK_STREAM, 0); memset(&sockAdr, 0, sizeof(sockAdr)); sockAdr.sin_family = AF_INET; sockAdr.sin_addr.s_addr = inet_addr(argv[1]); sockAdr.sin_port = htons(atoi(argv[2])); if (connect(hSock, (SOCKADDR*)&sockAdr, sizeof(sockAdr)) == SOCKET_ERROR) ErrorHandling("connect() error"); else puts("Connected ... ..."); while (1) { fputs("Input message(Q to quite):", stdout); fgets(msg, BUF_SZ, stdin); if (!strcmp(msg, "Q\n") || !strcmp(msg, "q\n")) { break; } strLen = strlen(msg); send(hSock, msg, strLen, 0); recvLen = 0; while (1) { recvLen = recv(hSock, msg, BUF_SZ - 1, 0); if (recvLen >= strLen) break; } msg[recvLen] = '\0'; printf("Message from server: %s", msg); } closesocket(hSock); WSACleanup(); system("PAUSE"); return 0; } void ErrorHandling(const char* msg) { fputs(msg, stderr); fputc('\n', stderr); system("PAUSE"); exit(-1); }
IOCP
#include <stdio.h> #include <stdlib.h> #include <process.h> #include <WinSock2.h> #include <Windows.h> #pragma warning(disable:4996) #pragma comment(lib, "ws2_32.lib") #define BUF_SZ 1024 #define READ 3 #define WRITE 5 typedef struct { SOCKET hClntSock; SOCKADDR_IN clntAdr; }PER_HANDLE_DATA, *LPPER_HANDLE_DATA; typedef struct { OVERLAPPED overlapped; WSABUF wsaBuf; char buffer[BUF_SZ]; int rwMode; }PER_IO_DATA, *LPPER_IO_DATA; unsigned WINAPI EchoThreadMain(LPVOID CompletionIO); void ErrorHandling(const char* msg); int main(int argc, char** argv) { WSADATA wsaData; HANDLE hComPort; SYSTEM_INFO sysInfo; LPPER_IO_DATA ioInfo; LPPER_HANDLE_DATA handleInfo; SOCKET hServSock; SOCKADDR_IN servAdr; DWORD recvBytes, flags = 0; int i; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error"); hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); GetSystemInfo(&sysInfo); for (i = 0; i < sysInfo.dwNumberOfProcessors; ++i) { _beginthreadex(NULL, 0, EchoThreadMain, (LPVOID)hComPort, 0, NULL); } hServSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); memset(&servAdr, 0, sizeof(servAdr)); servAdr.sin_family = AF_INET; servAdr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); servAdr.sin_port = htons(atoi(argv[1])); bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr)); listen(hServSock, 5); while (1) { SOCKET hClntSock; SOCKADDR_IN clntAdr; int adrLen = sizeof(clntAdr); hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &adrLen); handleInfo = (LPPER_HANDLE_DATA)malloc(sizeof(PER_HANDLE_DATA)); handleInfo->hClntSock = hClntSock; memcpy(&handleInfo->clntAdr, &clntAdr, adrLen); CreateIoCompletionPort((HANDLE)hClntSock, hComPort, (DWORD)handleInfo, 0); ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA)); memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED)); ioInfo->wsaBuf.len = BUF_SZ; ioInfo->wsaBuf.buf = ioInfo->buffer; ioInfo->rwMode = READ; WSARecv(handleInfo->hClntSock, &(ioInfo->wsaBuf), 1, &recvBytes, &flags, &(ioInfo->overlapped), NULL); } system("PAUSE"); return 0; } unsigned __stdcall EchoThreadMain(LPVOID CompletionIO) { HANDLE hComPort = (HANDLE)CompletionIO; SOCKET sock; DWORD bytesTrans; LPPER_HANDLE_DATA handleInfo; LPPER_IO_DATA ioInfo; DWORD flags = 0; while (1) { GetQueuedCompletionStatus(hComPort, &bytesTrans, (LPDWORD)&handleInfo, (LPOVERLAPPED*)&ioInfo, INFINITE); sock = handleInfo->hClntSock; if (ioInfo->rwMode = READ) { puts("message received!"); if (bytesTrans == 0) { closesocket(sock); free(handleInfo); free(ioInfo); continue; } memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED)); ioInfo->wsaBuf.len = bytesTrans; ioInfo->rwMode = WRITE; WSASend(sock, &(ioInfo->wsaBuf), 1, NULL, 0, &(ioInfo->overlapped), NULL); ioInfo = (LPPER_IO_DATA)malloc(sizeof(PER_IO_DATA)); memset(&(ioInfo->overlapped), 0, sizeof(OVERLAPPED)); ioInfo->wsaBuf.buf = ioInfo->buffer; ioInfo->wsaBuf.len = BUF_SZ; ioInfo->rwMode = READ; WSARecv(sock, &(ioInfo->wsaBuf), 1, NULL, &flags, &(ioInfo->overlapped), NULL); } else { puts("message sent!"); free(ioInfo); } } return 0; } void ErrorHandling(const char* msg) { fputs(msg, stderr); fputc('\n', stderr); system("PAUSE"); exit(-1); }
课后练习
1.完成端口对象将分配多个线程用于处理I/O。如何创建这些线程?如何分配?请从源码级别进行说明
由程序员自行创建调用WSASend
,WSARecv
等I/O函数的线程,只是该线程为了确认I/O的完成会调用GetQueuedCompetionStatus
函数。虽然任何线程都能调用GetQueuedCompletionStatus
函数,但实际得到I/O完成信息的线程数不会超过调用CreateIoCompletionPort
函数时指定的最大线程数。
2.CreateIoCompletionPort
函数与其他函数不同,提供2种功能。请问是哪2种?
Completion Port对象的产生
CP对象与套接字进行连接
3.完成端口对象和套接字之间的连接意味着什么?如何连接?
首先通过CreateIoCompletionPort
生成CP对象。之后再次使用CreateIoCompletionPort
函数将CP对象与套接字进行连接。
4.
c,d
5.
√,√,×