# 網路程式設計第十二周
## 觀念題
### 第一題: Threading優點
- 請說明多緒程式優點
- 將多核心CPU發揮得淋漓盡致
- 不同執行序能資料共享
- 有點像孫悟空的分身 + 情報共享
- 防止部分執行序卡住
### 第二題: Threading問題
- 資料被修改問題
- 以老師的程式碼來說
- 當這裡沒註解時

- 執行解果長這樣

- 是不正常的
- 但註解時呢

- 執行結果長這樣

- 是這樣的
- 原因是這樣的
- 這裡的程式長這樣

- 也因此這邊使用的參數是共用同一個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>
- 比對結果

## 心得
所以我是頭香嗎(X。
這周教了多序,他感覺是可以同時做到多程序並行的程式,感覺十分實用,而且程式碼也不會像之前寫得那麼噁心。
然後有關Thread可能會產生資料被修改問題,我個人粗淺的覺得如果向heap allocate多個變數不是就可以避免了嗎? 若是要做到資料共享,我只要將想要共享的參數使用static感覺上可以避免修改的行為@@
最後想問一下教授,Sleep(1)的用意似乎還是有點在賭電腦的效能,我在其他同學的電腦上發現他Sleep(1)似乎不會成功,想問一下教授,這是有方法解決的嗎?