51. 線程-服務端


在最開始實現併發服務端的時候, 最開始想到的辦法便是使用多進程, 使每個 TCP 連接對應一個進程; 之後我們又將多進程併發改寫成了 IO 複用的方式實現了相同的功能; 但現在唯一沒有將服務端改寫成線程, 本節就來改寫服務端.


線程服務端

在改寫成多線程之前, 要對 [線程創建], [線程同步]以及線程終止有所瞭解才行.


主函數僅僅只是將連接後回射操作交給線程即可 :

int main(int argc, char *argv[]){
	int sockfd, clientfd;
	socklen_t socklen;
	struct sockaddr_in servAddr, cliAddr;
	pthread_t tid;
	
	bzero(&servAddr, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_port = htons(atoi(argv[1]));
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	socklen = sizeof(cliAddr);

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd < 0)
		goto exit;
	if(bind(sockfd, (struct sockaddr* )&servAddr, sizeof(servAddr)) < 0)
		goto exit;

	listen(sockfd, 5);
	
	while(1){
		clientfd = accept(sockfd, (struct sockaddr *)&cliAddr, &socklen);
		if(clientfd < 0)
			goto exit;
        // 將回射功能交給線程
		if(pthread_create(&tid, NULL, echo, (void *)&clientfd))
			goto exit;
	}

exit:
	return 0;
}

線程函數就只是一個簡單的回射功能 :

void *echo(void *sockfd){
	// 使線程分離, 退出後直接釋放資源
	pthread_detach(pthread_self());

	char buf[BUFSIZE];
	int n;
	int fd = *(int *)sockfd;

	while(1){
		n = read(fd, buf, sizeof(buf));
		if(0 == n) break;
		write(fd, buf, n);
	}
	close(fd);
	
	pthread_exit((void *)0);
}

線程函數中注意需要執行 pthread_detach() 函數來完成線程分離, 這樣主進程就可以不執行 pthread_join 阻塞來回收線程的資源, 線程結束後自己回收資源.


服務端完整代碼 : service.c

服務端 :

./service 8080

客戶端完整代碼 : client.c

客服端 :

./client 127.0.0.1 8080

在這裏插入圖片描述


線程回射程序bug

在前面不管是多進程還是IO複用我們都或多或少遇到過問題, 那麼改寫成線程也同樣會有意想不到的問題.


上面的回射程序有一處很嚴重的bug.

pthread_create(&tid, NULL, echo, (void *)&clientfd)

我們函數參數傳入的是指針, 線程是併發執行, 對於參數 clientfd容易導致同步問題. 如果爲每次的clientfd 申請內存, 也會導致函數並非線程安全, 並且頻繁的申請也容易導致申請失敗同時內存釋放也是一個問題.


解決方法 :

  1. 加鎖, 保證同步. 但是使用鎖運行效率肯定就很低, 反而性能會降低.
  2. 封裝函數傳入形參或者結構體 . 一般都採用該呢方式, 也有更好的封裝和擴展性. 之後可以在線程池[1]中看到.

小結

  • 線程編程注意同步問題.
  • 可以將 web 客戶端 的改寫成線程, 這樣就可以不將 connect 設置成非阻塞.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章