linux C實現socket通信(單線程和多線程的實現)

socket通信是網絡編程的基礎,對於其概念的解釋以及知識點的介紹網上都寫的很詳細了,這裏不再囉嗦,這裏主要是基於linux實現簡單的客戶端服務器端通信,功能爲從客戶端輸入一行字符(可包含空格),服務器收到後進行顯示並將收到的字符串再發送給客戶端,並且服務器端可以一直對客戶端的連接進行監聽(這裏先只實現一個客戶端的連接,後面引入多線程,讓多個客戶端可以同時連接服務器端)。

服務器端流程爲創建socket->綁定->監聽->連接->通信->關閉socket

代碼裏有詳細註釋,上代碼更直接

服務器端:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

void handle_client(int clnt_sock){//
	int len;//收到的字節數
	printf("a new client connected!!!!!\n");
    //向客戶端發送數據
    char buff[100] = "hello I'm Server!!!\n";
    send(clnt_sock, buff, sizeof(buff),0);
	//memset(buff,0,sizeof(buff));
	while(1){
		if((len=recv(clnt_sock, buff, sizeof(buff),0))>0){
			if(strcmp(buff,"exit")==0){
				printf("client exit\n");
				break;
			}
			printf("receve from client:%s\n",buff);
			send(clnt_sock, buff, sizeof(buff),0);
			memset(buff,0,sizeof(buff));
		}
		else{
			printf("client closed\n");
			break;
		}
	}
    //關閉套接字
    close(clnt_sock);
	printf("clnt_socket closed\n");
}

int main(){
    //創建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	printf("create socket success!\n");
    //將套接字和IP、端口綁定
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每個字節都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));//綁定
	printf("bind success!!\n");
    //進入監聽狀態,等待用戶發起請求
    listen(serv_sock, 20);
	printf("listening......\n");
    //接收客戶端請求
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    while(1){
        int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);//接收客戶端的連接請求,如果沒有客戶端連接,則程序會在這裏阻塞,直到有客戶端的連接到來
		handle_client(clnt_sock);//爲客戶端的服務函數
    }
    close(serv_sock);
    return 0;
}

客戶端:

#include <stdio.h>
#include <string.h> 
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(){
    //創建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
	printf("create socket success!\n");
    //向服務器(特定的IP和端口)發起請求
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每個字節都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址(服務器的)
    serv_addr.sin_port = htons(1234);  //端口(與服務器對應)
    if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))<0){//連接
		printf("connect failed!!!\n");
		return 0;
	}
	printf("connect success!!!\n");
    //讀取服務器傳回的數據
    char buffer[100];
    recv(sock, buffer, sizeof(buffer),0);
   
    printf("Message form server: %s\n", buffer);
    while(1){
		memset(buffer,0,sizeof(buffer));
		fgets(buffer,sizeof(buffer),stdin);//fgets會讀取換行符\n
		buffer[strlen(buffer)-1]='\0';//去掉換行符
		send(sock, buffer, sizeof(buffer),0);
		//sleep(1);
		recv(sock, buffer, sizeof(buffer),0);
		printf("Message form server: %s\n", buffer);
	}
    //關閉套接字
    close(sock);
    return 0;
}

這裏有幾個點需要格外小心

    1.服務器和客戶端收發數據時一定要注意字節的大小對應,即send的第三個參數和recv的第三個參數一致,否則會出問題

    2.服務器端和客戶端手法數據除了send/recv這一對外還可以用sendmsg/recvmsg以及read/write

    3.scanf不能讀取空格,fgets可以讀帶空格的輸入,但會將換行符也讀入

此外,在服務器端執行到accept時,若沒有客戶端連接,則程序會阻塞,直到有客戶端的連接請求到來。同樣,在服務器或客戶端的recv時,程序也會阻塞,直到緩衝區裏的數據傳輸結束(通過對應協議判斷),具體可以去百度recv的實現原理(https://blog.csdn.net/boiled_water123/article/details/82181368這裏有介紹),這裏就不詳細介紹了

--------------------------------------------------------------------------------------------------------------------------------------------

更新一下引入多線程後的服務器端,可以接受多個客戶端的連接,併爲每一個客戶端單獨開一個線程去處理與客戶端的業務,客戶端不需要改變,只需要在服務器端小修改就可以了,先上代碼吧。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

int client_num=0;//當前與服務器端連接的客戶端的數目

void* handle_client(void* arg){//參數與返回值都必須是void*類型(線程函數的特點)
	int len;//收到的字節數
	int* clnt_sock = (int*)arg;
	printf("a new client connected!!!!!,total %d client connected now\n",client_num);
    //向客戶端發送數據
    char buff[100] = "hello I'm Server!!!\n";
    send(*clnt_sock, buff, sizeof(buff),0);
	//memset(buff,0,sizeof(buff));
	while(1){
		if((len=recv(*clnt_sock, buff, sizeof(buff),0))>0){
			if(strcmp(buff,"exit")==0){
				printf("client exit\n");
				break;
			}
			printf("receve from client:%s\n",buff);
			send(*clnt_sock, buff, sizeof(buff),0);
			memset(buff,0,sizeof(buff));
		}
		else{
			printf("client closed\n");
			break;
		}
	}
    //關閉套接字
    close(*clnt_sock);
	client_num--;
	printf("clnt_socket closed,remain total %d client connected now\n",client_num);
}

int main(){
	pthread_t tidp;//線程的id

    //創建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	printf("create socket success!\n");
    //將套接字和IP、端口綁定
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每個字節都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址
    serv_addr.sin_port = htons(1234);  //端口
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
	printf("bind success!!\n");
    //進入監聽狀態,等待用戶發起請求
    listen(serv_sock, 3);
	printf("listening......\n");
    //接收客戶端請求
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    while(1){
        int* clnt_sock = (int *)malloc(sizeof(int));
		*clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
		if((pthread_create(&tidp, NULL, handle_client, (void*)clnt_sock)) == -1){//客戶端來一個請求就創建一個線程
			printf("create error!\n");
		}
		else{
			printf("create success!\n");
			client_num++;
		}
    }
	
    close(serv_sock);//關閉服務器端
    return 0;
}

代碼裏註釋很詳細,這裏就不一一解釋了,重點是大家要了解線程的創建(pthread_creat)以及線程函數的寫法,同時,需要注意一下引入多線程後再linux上編譯時需要加上-lpthread才能編譯通過,例如: gcc mult_muti_thread_server.c -lpthread -o muti_thread_server

最後,程序還可以進一步完善,比如在主線程中記錄所有在運行的線程號(用循環數組可以實現)當有線程退出時,找到對應的線程號並刪除,數組氣候的元素一次左移一位,這樣可以限制連接服務器的客戶端數目。同時,也可以在主線程發生異常需要推出前執行pthread_join等待所有子線程退出(斷開所有客戶端的連接)後再退出主線程。程序還有很大的優化空間,歡迎大家探討。

---------------------------------------------------分界線-------------------------------------------------

突然發現一個大問題-------內存泄漏!!!!!

分析上面mult_muti_thread_server.c代碼,在主線程裏每有一個新的客戶端連接,就會在堆區開闢一塊內存int* clnt_sock = (int *)malloc(sizeof(int));然而在後面客戶端斷開連接時卻並沒有釋放掉free,這樣就內存泄漏了,這裏就不直接修改上面代碼了,以警示自己,避免後面踩坑。在下面貼出釋放內存的代碼片段:在handle_client函數中斷開套接字後增加釋放內存的操作

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章