1. 程式人生 > >Socket程式設計之一個埠能建立多個TCP連線?

Socket程式設計之一個埠能建立多個TCP連線?

一、背景

記得上學期暑假的時候我基於MFC寫了一個簡單的聊天程式。那個聊天程式,兩部分組成,監聽客戶端請求執行緒和客戶端請求處理執行緒。

1.伺服器接收到登陸請求,驗證登陸資訊後,如果通過驗證建立新執行緒與其互動,並通知使用者連線到新的埠,並建立好新埠的SOCKET連線。

2.然後將使用者類和新埠傳給新建立的客戶端請求處理執行緒。

當時,可能是沒理解好的原因,誤以為,一個埠同一時間只能建立起一個TCP連線。所以寫這個聊天程式時才會每一個使用者分配一個新的埠。

之前,自己瞭解HTTP後,接觸到web應用開發的時候,就疑惑了,web server接收瀏覽器的請求,都是從80埠接受請求。當時沒仔細去想,就以為web server和我那個聊天程式一樣,會去建立新的執行緒與其進行請求處理。

二、問題

最近,寫爬蟲的時候用到了Smsniff去抓包。發現,一個http請求中。往往是隻與伺服器的80埠進行通訊。這就與我記憶中的SOCKET衝突了。於是今天寫了個小程式碼測試了一下,一個埠,真的能建立多個連線。

三、程式碼邏輯流程

1.server

①服務端主執行緒:負責監聽5174埠,如果有請求,accept到系統分配的SOCKET(為unsigned int, recv接受函式就需要這個SOCKET)於是建立一個新執行緒,將這個SOCKET通過lpParament傳遞給新執行緒。

②伺服器資訊接受執行緒:負責從lpParment從拿到SOCKET並,recv客戶端發來的資訊。

2.client

連線到伺服器的5174埠,併發送訊息。

四、執行結果

一個埠的確能同時建立多條TCP請求。

五、理解

綜合部分網上看到的資料。我的理解是,一個連線的唯一標識是[server ip, server port, client ip, client port]也就是說。作業系統,接收到一個埠發來的資料時,會在該埠,產生的連線中,查詢到符合這個唯一標識的並傳遞資訊到對應緩衝區。

1.一個埠同一時間只能bind給一個SOCKET。就是同一時間一個埠只可能有一個監聽執行緒(監聽listen之前要bind)。

2.為什麼一個埠能建立多個TCP連線,同一個埠也就是說 server ip和server port 是不變的。那麼只要[client ip 和 client port]不相同就可以了。能保證接唯一標識[server ip, server port, client ip, client port]的唯一性。

六、疑問解答

1.如果監聽的執行緒釋放掉監聽用的SOCKET了,會影響之前通過這個監聽SOCKET建立的TCP連線麼?

答案:並不會,SOCKET之間是獨立的,不會有影響(我已經自己寫了程式驗證了,讀者可以自己寫程式碼驗證)。

2.一個埠能建立多個UDP連線麼?

答案:UPD本身就是無連線的。所以不存在什麼多個UDP連線。只是,服務端接收UDP資料需要bind一個埠。一個SOCKET只能繫結到一個埠。

七、服務端和客戶端程式碼

注意:如果用的是VS,記得把 SDL checks(安全開發生命週期檢測關閉)

服務端:

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib,"Ws2_32.lib")
#include <memory.h>

#define BUF_SIZE 4096
#define QUEUE_SIZE 5

DWORD WINAPI ThreadProcServerConmunicate(
	_In_ LPVOID lpParameter
) {
	SOCKET * psc = (SOCKET *)lpParameter;

	int receByt = 0;
	while (1)
	{
		char buf[BUF_SIZE];
		receByt = recv(*psc, buf, BUF_SIZE, 0);
		buf[receByt] = '\0';
		if (receByt>0)
		{
			printf("%u : 接收的訊息是:%s\n", *psc, buf);
		}
		else
		{
			printf("接收訊息結束!");
			break;
		}

	}
	int ic = closesocket(*psc);
	free(psc);
	return 0;
}


int main() {
	
	WSADATA wsd;
	WSAStartup(MAKEWORD(2, 0), &wsd);

	SOCKET s = NULL;
	s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	struct sockaddr_in ch;
	memset(&ch, 0, sizeof(ch));
	ch.sin_family = AF_INET;
	ch.sin_addr.s_addr = INADDR_ANY;
	ch.sin_port = htons(5174);
	int b = bind(s, (struct sockaddr *) &ch, sizeof(ch));
	
	int l = listen(s, QUEUE_SIZE);
	printf("正在監聽本機的5174埠\n");

	while (1) {
		SOCKET * psc = (SOCKET *)malloc(sizeof(SOCKET));
		*psc = accept(s, 0, 0);
		printf("一個客戶端已經連線到本機的5174埠,SOCKET是 : %u \n", *psc);

		CreateThread(NULL,
			0,
			&ThreadProcServerConmunicate,
			psc,
			0,
			NULL
		);
	}
	
	int is = closesocket(s);
	WSACleanup();
	return 0;
}

客戶端:

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib,"Ws2_32.lib")
#include <memory.h>

#define BUF_SIZE 4096

void main()
{
	WSADATA wsd;
	WSAStartup(MAKEWORD(2, 0), &wsd);

	SOCKET s = NULL;
	s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	struct sockaddr_in ch;
	memset(&ch, 0, sizeof(ch));
	ch.sin_family = AF_INET;
	ch.sin_addr.s_addr = inet_addr("127.0.0.1");
	ch.sin_port = htons(5174);

	int c = connect(s, (struct sockaddr *) &ch, sizeof(ch));
	printf("已經連線到伺服器的5174埠,現在可以向伺服器傳送訊息了!\n");

	char info[1024], buf[BUF_SIZE];

	while (1)
	{
		gets(info);
		if (info[0] == '\0')
			break;
		strcpy(buf, info);
		int nsend = send(s, buf, strlen(buf), 0);
		Sleep(500);
	}
	int ic = closesocket(s);
	WSACleanup();
	return 0;
}