# 網路程式設計第十二周 ## 觀念題 ### 第一題: Threading優點 - 請說明多緒程式優點 - 將多核心CPU發揮得淋漓盡致 - 不同執行序能資料共享 - 有點像孫悟空的分身 + 情報共享 - 防止部分執行序卡住 ### 第二題: Threading問題 - 資料被修改問題 - 以老師的程式碼來說 - 當這裡沒註解時 ![image](https://hackmd.io/_uploads/HyUzrybXR.png) - 執行解果長這樣 ![image](https://hackmd.io/_uploads/rJqDrkWQ0.png) - 是不正常的 - 但註解時呢 ![image](https://hackmd.io/_uploads/Bk0nSJb70.png) - 執行結果長這樣 ![image](https://hackmd.io/_uploads/Sy_JIk-QC.png) - 是這樣的 - 原因是這樣的 - 這裡的程式長這樣 ![image](https://hackmd.io/_uploads/H1dHIybQR.png) - 也因此這邊使用的參數是共用同一個referance - 因此若是使用Sleep時,在ThreadMain1數值將會修改 - ~~講白點就是別名的壞處拉哈哈~~ - 但我粗淺的覺得,這樣是一個變數汙染的行為 - 如果要達到變數共用,但避免變數汙染我認為可以用以下方式解決 - 變數汙染問題: 宣告不同的變數,傳入參數 - 變數共用問題: 使用static,可以做到 - 程式碼如下 :::spoiler C ```C= #include <stdio.h> #include <winsock.h> void *ThreadMain1(void *arg); /* Main program of a thread */ void *ThreadMain2(void *arg); /* Main program of a thread */ /* Structure of arguments to pass to client thread */ struct ThreadArgs { static int a; static int b; int c; }; int ThreadArgs::a = 5; int ThreadArgs::b = 3; int main() { struct ThreadArgs *threadArgs1, *threadArgs2; /* Pointer to argument structure for thread */ int threadID1,threadID2; /* Thread ID from CreateThread() */ threadArgs1 = (struct ThreadArgs *) malloc(sizeof(struct ThreadArgs)); threadArgs2 = (struct ThreadArgs *) malloc(sizeof(struct ThreadArgs)); printf("Hello World from main()\n"); if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) ThreadMain1, threadArgs1, 0, (LPDWORD) &threadID1) == NULL) printf("CreateThread() failed"); printf("Thread %d created.\n", threadID1); Sleep(1000); if (CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) ThreadMain2, threadArgs2, 0, (LPDWORD) &threadID2) == NULL) printf("CreateThread() failed"); printf("Thread %d created.\n", threadID2); Sleep(2000); free(threadArgs1); /* Deallocate memory for argument */ free(threadArgs2); /* Deallocate memory for argument */ return 0; } void *ThreadMain1(void *threadArgs) { int i,j,k; i = ((struct ThreadArgs *) threadArgs) -> a; j = ((struct ThreadArgs *) threadArgs) -> b; k = i + j; ((struct ThreadArgs *) threadArgs) -> c = k; Sleep(2000); printf("%d + %d = %d \n",i,j,((struct ThreadArgs *) threadArgs) -> c); } void *ThreadMain2(void *threadArgs) { int i,j,k; i = ((struct ThreadArgs *) threadArgs) -> a; j = ((struct ThreadArgs *) threadArgs) -> b; k = i - j; ((struct ThreadArgs *) threadArgs) -> c = k; printf("%d - %d = %d \n",i,j,k); } ``` ::: ## 實作題: 傳送文字檔案的client/server程式,加入多緒功能 - client與server之間,會建立兩條TCP連線,建立兩個thread,分別傳送文字檔的奇數行與偶數行 - 接收端會將收到的內容整合為一個檔案。比對傳送前後的文字檔要相同。 :::info - 測試文檔 [Alice.txt](https://gist.githubusercontent.com/phillipj/4944029/raw/75ba2243dd5ec2875f629bf5d79f6c1e4b5a8b46/alice_in_wonderland.txt) - 文檔比對script :::spoiler Python ```Python= sendFile = open("in.txt", "r") ReceveFile = open("out.txt", "r") if sendFile.readlines() == ReceveFile.readlines(): print("Files are same") else: print("Files are different") ``` ::: ::: - 程式碼 - Server :::spoiler C++ ```C++= #include <iostream> #include <fstream> #include <winsock.h> using namespace std; void *ThreadMain(void *arg); struct ThreadArgs{ SOCKET oddSocket; SOCKET evenSocket; bool isEven; }; void* ThreadMain(void*); int main(){ /* * Initialize Winsock */ WSADATA wsaData; WSAStartup(0x101,(LPWSADATA) &wsaData); /* Server Information * IP: 127.0.0.1 * Port: 1234 */ sockaddr_in server; server.sin_family = AF_INET; server.sin_addr.s_addr = inet_addr("127.0.0.1"); server.sin_port = htons(1234); /* Create a socket */ SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0); bind(serverSocket, (sockaddr*)&server, sizeof(server)); listen(serverSocket, 5); while (TRUE){ SOCKET oddSocket = accept(serverSocket, NULL, NULL); SOCKET evenSocket = accept(serverSocket, NULL, NULL); ThreadArgs* args = (ThreadArgs*) malloc(sizeof(ThreadArgs)); args->oddSocket = oddSocket; args->evenSocket = evenSocket; HANDLE Thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadMain, args, 0, NULL); Sleep(1); } return 0; } void* ThreadMain(void* arg){ ThreadArgs *args = (ThreadArgs*) arg; SOCKET oddSocket = args->oddSocket; SOCKET evenSocket = args->evenSocket; ofstream out("out.txt"); bool oddEnd = false; bool evenEnd = false; while (true){ if (oddEnd && evenEnd){ break; } char buffer[1024]; if (recv(oddSocket, buffer, sizeof(buffer), 0) <= 0){ oddEnd = true; } else{ out << buffer; } if (recv(evenSocket, buffer, sizeof(buffer), 0) <= 0){ evenEnd = true; } else{ out << buffer; } if (oddEnd && evenEnd){ break; } } closesocket(oddSocket); closesocket(evenSocket); return 0; } ``` ::: :::spoiler Python ```python= import socket import threading def receive_message(odd: socket.socket , even:socket.socket) -> None: file = open("out.txt","w") oddEnd, evenEnd = False, False while True: print(oddEnd, evenEnd) if oddEnd and evenEnd: break try: data = odd.recv(1024) if not data: raise ConnectionResetError file.write(data.decode()) except ConnectionResetError: oddEnd = True try: data = even.recv(1024) if not data: raise ConnectionResetError file.write(data.decode()) except ConnectionResetError: evenEnd = True odd.close() even.close() file.close() server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind(("127.0.0.1",1234)) server.listen(5) while True: odd ,addr = server.accept() even,addr = server.accept() recive = threading.Thread(target= receive_message, args=(odd,even)) recive.start() ``` ::: - Client :::spoiler C++ ```C++= #include <iostream> #include <fstream> #include <winsock.h> #include <string> using namespace std; void *ThreadMain(void *arg); struct ThreadArgs{ SOCKET serverSocket; bool isEven; }; int main(){ /* * Initialize Winsock */ WSADATA wsaData; WSAStartup(0x101,(LPWSADATA) &wsaData); /* Server Information * IP: 127.0.0.1 * Port: 1234 */ sockaddr_in server; server.sin_family = AF_INET; server.sin_addr.s_addr = inet_addr("127.0.0.1"); server.sin_port = htons(1234); /* Create a socket */ SOCKET oddSocket = socket(AF_INET, SOCK_STREAM, 0); SOCKET evenSocket = socket(AF_INET, SOCK_STREAM, 0); /* Connect to the server */ connect(oddSocket, (sockaddr*)&server, sizeof(server)); connect(evenSocket, (sockaddr*)&server, sizeof(server)); /* Create a thread for each socket */ ThreadArgs* oddArgss = (ThreadArgs*) malloc(sizeof(ThreadArgs)); oddArgss->serverSocket = oddSocket; oddArgss->isEven = false; ThreadArgs* evenArgs = (ThreadArgs*) malloc(sizeof(ThreadArgs)); evenArgs->serverSocket = evenSocket; evenArgs->isEven = true; HANDLE oddThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadMain, oddArgss, 0, NULL); Sleep(1); HANDLE evenThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadMain, evenArgs, 0, NULL); Sleep(1); Sleep(1000 * 10000); return 0; } void *ThreadMain(void *arg){ ThreadArgs *args = (ThreadArgs*) arg; SOCKET serverSocket = args->serverSocket; bool isEven = args->isEven; ifstream inFile; inFile.open("in.txt", ios::in); if (inFile.fail()) exit(1); string buffer; int line = 0; while (!inFile.eof()){ getline(inFile, buffer); if (inFile.peek() != EOF){ buffer += '\n'; } line ++ ; if (line % 2 == 0 && isEven){ send(serverSocket, buffer.c_str(), buffer.size()+1, 0); cout << "Sent2: " << buffer << endl; } else if (line % 2 != 0 && !isEven){ send(serverSocket, buffer.c_str(), buffer.size()+1, 0); cout << "Sent1: " << buffer << endl; } Sleep(100); } inFile.close(); closesocket(serverSocket); free(arg); return 0; } ``` ::: :::spoiler Python ```python= import socket import threading import time def send_message(socket: socket.socket, isEven: bool) -> None: file = open("in","r") read = file.readlines() for i, line in enumerate(read, start= 1): if isEven: if i%2 == 0: socket.send(line.encode()) print(f"sent0: {line}") else: if i%2 != 0: socket.send(line.encode()) print(f"sent1: {line}") time.sleep(0.1) socket.close() file.close() return None """ Server Information: IP: 127.0.0.1 PORT: 1234 """ IP,PORT = "127.0.0.1",1234 """ Create socket odd: send file in odd even: send file in even """ odd = socket.socket(socket.AF_INET,socket.SOCK_STREAM) odd.connect((IP,PORT)) even = socket.socket(socket.AF_INET,socket.SOCK_STREAM) even.connect((IP,PORT)) first = threading.Thread(target=send_message,args=(odd,False)) first.start() time.sleep(0.001) second = threading.Thread(target=send_message,args=(even,True)) second.start() first.join() second.join() ``` ::: - 執行畫面 - 教你七分鐘看完愛麗絲夢遊仙境 <iframe width="560" height="315" src="https://www.youtube.com/embed/y5dqbJA_ybM?si=h44eoWEE3H3g3Ssa" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> - 比對結果 ![image](https://hackmd.io/_uploads/S1pUHlbXC.png) ## 心得 所以我是頭香嗎(X。 這周教了多序,他感覺是可以同時做到多程序並行的程式,感覺十分實用,而且程式碼也不會像之前寫得那麼噁心。 然後有關Thread可能會產生資料被修改問題,我個人粗淺的覺得如果向heap allocate多個變數不是就可以避免了嗎? 若是要做到資料共享,我只要將想要共享的參數使用static感覺上可以避免修改的行為@@ 最後想問一下教授,Sleep(1)的用意似乎還是有點在賭電腦的效能,我在其他同學的電腦上發現他Sleep(1)似乎不會成功,想問一下教授,這是有方法解決的嗎?