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.
√,√,×