計算機網路課設:網路聊天程式的設計與實現(C語言實現)
阿新 • • 發佈:2021-01-06
經過和課設老師的一番對線,java寫的版本最後以失敗告終。。。
是的同學都來嘲諷我了:
沒辦法了只能再用C語言寫一遍。課設指導書附錄一的程式碼是阻塞式的,也就是一個服務端+一個客戶端,兩個互相發信息。但缺陷是一端只能進行一項功能:要麼發要麼收。也就是說一端在另一端準備發訊息的時候會阻塞,只能是接受訊息的狀態。所以不能兩端同時發訊息。經過友人指點,用C語言的執行緒就能很好地解決這個問題。
服務端(主執行緒開啟了兩個子執行緒):
#include <sys/stat.h> #include <fcntl.h> #include <winsock2.h> #include <windows.h> #pragma comment(lib, "wsock32.lib") #include <errno.h> #include<stdlib.h> #include<string.h> #include <sys/types.h> #include<ws2tcpip.h> #include <stdio.h> #include <unistd.h> #include <process.h> #define SERVER_PORT 6666 //全域性變數 int client; int iDataNum; char buffer[200]; //儲存 傳送和接收的資訊 void rece(){//形參void * 不可省略,否則編譯出錯 while(1){ buffer[0] = '\0'; iDataNum = recv(client, buffer, 1024, 0); if(iDataNum < 0) // perror("recv null"); continue; else{ buffer[iDataNum] = '\0'; if(strcmp(buffer, "quit") == 0) break; printf("收到來自客戶端的訊息: %s\n", buffer); } Sleep(3000); } printf("後臺執行緒結束\n\n"); _endthread(); } void sendm(){//形參void * 不可省略,否則編譯出錯 while(1){ scanf("%s", buffer); send(client, buffer, strlen(buffer), 0); //服務端也向客戶端傳送訊息 // if(strcmp(buffer, "quit") == 0) break;輸入quit停止服務端程式 } } int main() { //呼叫socket函式返回的檔案描述符 int serverSocket; //宣告兩個套接字sockaddr_in結構體變數,分別表示客戶端和伺服器 struct sockaddr_in server_addr; struct sockaddr_in clientAddr; int addr_len = sizeof(clientAddr); //必須先初始化 WSADATA wsaData; WSAStartup(MAKEWORD(2,2),&wsaData); if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) !=2){ printf("require version fail!"); return -1; } //socket函式,失敗返回-1 //int socket(int domain, int type, int protocol); //第一個引數表示使用的地址型別,一般都是ipv4,AF_INET //第二個引數表示套接字型別:tcp:面向連線的穩定資料傳輸SOCK_STREAM //第三個引數設定為0 //建立socket if((serverSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0) { perror("socket"); return 1; } //初始化 server_addr memset(&server_addr,0, sizeof(server_addr)); //初始化伺服器端的套接字,並用htons和htonl將埠和地址轉成網路位元組序 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); //ip可是是本伺服器的ip,也可以用巨集INADDR_ANY代替,代表0.0.0.0,表明所有地址 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //對於bind,accept之類的函式,裡面套接字引數都是需要強制轉換成(struct sockaddr *) //bind三個引數:伺服器端的套接字的檔案描述符, if(bind(serverSocket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("connect"); return 1; } //設定伺服器上的socket為監聽狀態,5為最大的客戶端執行緒數 if(listen(serverSocket, 5) < 0) { perror("listen"); return 1; } //迴圈 接收訊息、傳送訊息 while(1) { printf("監聽埠: %d\n", SERVER_PORT); //呼叫accept函式後,會進入阻塞狀態 //accept返回一個套接字的檔案描述符,這樣伺服器端便有兩個套接字的檔案描述符, //serverSocket和client。 //serverSocket仍然繼續在監聽狀態,client則負責接收和傳送資料 //clientAddr是一個傳出引數,accept返回時,傳出客戶端的地址和埠號 //addr_len是一個傳入-傳出引數,傳入的是呼叫者提供的緩衝區的clientAddr的長度,以避免緩衝區溢位。 //傳出的是客戶端地址結構體的實際長度。 //出錯返回-1 client = accept(serverSocket, (struct sockaddr*)&clientAddr, (socklen_t*)&addr_len); if(client < 0) { perror("accept"); continue; } printf("等待訊息...\n"); //inet_ntoa ip地址轉換函式,將網路位元組序IP轉換為點分十進位制IP //表示式:char *inet_ntoa (struct in_addr); printf("IP is %s\n", inet_ntoa(clientAddr.sin_addr)); //把來訪問的客戶端的IP地址打出來 printf("Port is %d\n", htons(clientAddr.sin_port)); _beginthread(rece,0,NULL);//開啟一個接受訊息執行緒 _beginthread(sendm,0,NULL);//開啟一個傳送訊息執行緒 } close(serverSocket); return 0; }
客戶端(主執行緒只開啟了一個接收訊息執行緒,主執行緒負責發訊息給服務端):
#include <sys/stat.h> #include <fcntl.h> #include <winsock2.h> #include <windows.h> #pragma comment(lib, "wsock32.lib") #include <errno.h> #include<stdlib.h> #include<string.h> #include <sys/types.h> #include<ws2tcpip.h> #include <stdio.h> #include <unistd.h> #include <process.h> #define SERVER_PORT 6666 char recvbuf[200]; //儲存 接收到的資訊 int iDataNum; //客戶端只需要一個套接字檔案描述符,用於和伺服器通訊 int serverSocket; void rece(){//形參void * 不可省略,否則編譯出錯 while(1){ recvbuf[0] = '\0'; iDataNum = recv(serverSocket,recvbuf,200,0); //接收服務端發來的訊息,返回資訊長度 recvbuf[iDataNum] = '\0'; if(iDataNum>0) { printf("收到來自伺服器的訊息: %s\n",recvbuf); } Sleep(3000);//執行緒休眠3s,表示每隔3s檢視一次是否有來自客戶端的訊息 } printf("後臺執行緒結束\n\n"); _endthread(); } int main() { //描述伺服器的socket struct sockaddr_in serverAddr; char sendbuf[200]; //儲存 傳送的資訊 //下面程式碼初始化 WSADATA wsaData; WSAStartup(MAKEWORD(2,2),&wsaData); if(LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) !=2){ printf("require version fail!"); return -1; } if((serverSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) < 0) { perror("socket"); return 1; } //描述服務端的serverAddr,其中SERVER_PORT為6666 serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(SERVER_PORT); //指定伺服器端的ip,本地測試:127.0.0.1 //inet_addr()函式,將點分十進位制IP轉換成網路位元組序IP serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if(connect(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) { perror("connect"); return 1; } printf("連線到主機...\n"); _beginthread(rece,0,NULL);//開啟一個接收訊息執行緒 while(1) { scanf("%s", sendbuf); send(serverSocket, sendbuf, strlen(sendbuf), 0); //向服務端傳送訊息 if(strcmp(sendbuf, "quit") == 0) break;. } close(serverSocket); return 0; }
程式碼是我在另一篇文章的基礎上增加了執行緒的功能後寫的,附上博文連結:https://www.cnblogs.com/fisherss/p/12085123.html
效果圖(只有收訊息有文字提示,發訊息無文字提示):