1. 程式人生 > 其它 >計算機網路課設:網路聊天程式的設計與實現(C語言實現)

計算機網路課設:網路聊天程式的設計與實現(C語言實現)

技術標籤:課設或專案實戰網路socketc++

經過和課設老師的一番對線,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

效果圖(只有收訊息有文字提示,發訊息無文字提示):