TCP 服務器端和客戶端程序設計

1設計思路

(1) socket函數:

爲了執行網絡輸入輸出,一個進程必須作的第一件事就是調用socket函數得到一個文件描述符。windows

int socket(int family,int type,int protocol);  //失敗返回-1,並設置errno爲相應的值,其它值爲成功 

第一個參數指明網絡層協議簇,目前支持5種,最經常使用的有AF_INET(IPv4協議)和AF_INET6(IPv6協議);服務器

第二個參數指明套接口類型,有四種類型可選:SOCK_STREAM(字節流套接口)、SOCK_DGRAM(數據報套接口)、SOCK_SEQPACKET(有序分組)和SOCK_RAW(原始套接口)。SOCK_STREAM基於TCP,是有保障的、面向鏈接的SOCKET,多用於資料(如文件)傳送;SOCK_DGRAM基於UDP,是無保障的面向消息的socket , 主要用於在網絡上發廣播信息。網絡

第三個參數指明相應的傳輸協議,常見的協議有TCP、UDP、SCTP,要指定它們分別使用宏IPPROTO_TCP、IPPROTO_UPD、IPPROTO_SCTP來指定,若是該值爲0則使用默認值。socket

(2) 套接口地址結構:

這些地址結構的名字均已「sockaddr_」開頭,並以對應每一個協議族的惟一後綴結束。以IPv4套接口地址結構爲例,它以「sockaddr_in」命名,如下是結構體的內容:tcp

struct in_addr {
    in_addr_t s_addr;     /* IPv4地址 */
}; 

struct sockaddr_in {
    uint8_t sin_len;        /* 無符號的8位整數 */
    sa_family_t sin_family; /* 套接口地址結構的地址簇,這裏爲AF_INET */
    in_port_t sin_port;     /* TCP或UDP端口 */
    struct in_addr sin_addr;
    char sin_zero[8];  
};

(3) setsockopt和getsockopt函數:

(1) setsockopt()函數,用於任意類型、任意狀態套接口的設置選項值。函數

int setsockopt(int scokfd,int level,int name,char *value,int *optlen);

(2) getsockopt()用於獲取任意類型、任意狀態套接口的選項當前值,並把結果存入optval。ui

int getsockopt(int sockfd,int level,int name,char *value,int optlen);

這兩個函數參數相同以下:url

第一個參數sockfd是套接字描述符,對於服務器是accept()函數返回的已鏈接套接字描述符,對於客戶端是調用socket()函數返回的套接字描述符spa

第二個參數level是選項定義的層次,支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6設計

第三個參數optname是需設置的選項

第四個參數optval是指針,指向存放選項待設置的新值的緩衝區

第五個參數optlen是optval緩衝區長度

( 4 ) bind函數:

爲套接口分配一個本地IP和協議端口,對於網際協議,協議地址是32位IPv4地址或128位IPv6地址與16位的TCP或UDP端口號的組合;如指定端口爲0,調用bind時內核將選擇一個臨時端口,若是指定一個通配IP地址,則要等到創建鏈接後內核才選擇一個本地IP地址。

int bind(int sockfd, const struct sockaddr * server, socklen_t addrlen); //返回0表示成功,-1表示失敗 

第一個參數是socket函數返回的套接口描述字;

第二個和第三個參數分別是一個指向特定於協議的地址結構的指針和該地址結構的長度。

(5) listen函數:

listen函數僅被TCP服務器調用,它的做用是將用sock建立的主動套接口轉換成被動套接口,並等待來自客戶端的鏈接請求。

int listen(int sockfd,int backlog); //返回0表示成功,返回-1表示失敗

第一個參數是socket函數返回的套接口描述字;

第二個參數規定了內核爲此套接口排隊的最大鏈接個數。因爲listen函數第二個參數的緣由,內核要維護兩個隊列:已完成鏈接隊列和未完成鏈接隊列。未完成隊列中存放的是TCP鏈接的三路握手未完成的鏈接,accept函數是從已完成隊列中取鏈接返回給進程;當已完成隊列爲空時,進程將進入睡眠狀態。

( 6 ) accept函數:

accept函數由TCP服務器調用,從已完成鏈接隊列頭返回一個已完成鏈接,若是完成鏈接隊列爲空,則進程進入睡眠狀態。

int accept(int listenfd, struct sockaddr *client, socklen_t * addrlen); //返回值非負表示成功,-1表示失敗

第一個參數是socket函數返回的套接口描述字;第二個和第三個參數分別是一個指向鏈接方的套接口地址結構和該地址結構的長度;該函數返回的是一個全新的套接口描述字;若是對客戶段的信息不感興趣,能夠將第二和第三個參數置爲空。

( 7 ) write和read函數:

當服務器和客戶端的鏈接創建起來後,就能夠進行數據傳輸了,服務器和客戶端用各自的套接字描述符進行讀/寫操做。由於套接字描述符也是一種文件描述符,因此能夠用文件讀/寫函數write()和read()進行接收和發送操做。
(1) write()函數用於數據的發送。

int write(int sockfd, char *buf, int len); //返回值非負表示成功,-1表示失敗

(2) read()函數用於數據的接收。

int read(int sockfd, char *buf, int len); //返回值非負表示成功,-1表示失敗

兩個函數參數相同以下:

第一個參數sockfd是套接字描述符,對於服務器是accept()函數返回的已鏈接套接字描述符,對於客戶端是調用socket()函數返回的套接字描述符;

第二個參數buf是指向一個用於發送 / 接收信息的數據緩衝區;

第三個參數len指明發送 / 接收數據緩衝區的大小。

( 8 ) send和recv函數:

TCP套接字提供了send()和recv()函數,用來發送和接收操做。這兩個函數與write()和read()函數很類似,只是多了一個附加的參數。
(1) send()函數用於數據的發送。

ssize_t send(int sockfd, const void *buf, size_t len, int flags); //成功返回寫出的字節數,失敗返回-1

前3個參數與write()相同,參數flags是傳輸控制標誌。
(2) recv()函數用於數據的發送。

ssize_t recv(int sockfd, void *buf, size_t len, int flags); //成功返回讀入的字節數,失敗返回-1

前3個參數與read()相同,參數flags是傳輸控制標誌。

( 9 ) close函數:用於關閉socket

2實現代碼

TCP服務器端:Windows和Linux皆可

// ServerMain.cpp : Linux/windows Server
// socket()->bind()->listen()->accept()->read()->write()->close()
#include <stdint.h>
#include <stdio.h>

#ifdef _MSC_VER
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#endif

bool CloseSerSocket(uint32_t dwSockId,const char* strError)
{
#ifdef _MSC_VER
	closesocket(dwSockId);
#else
	close(dwSockId);
#endif 
	if (NULL != strError)
	{
		perror(strError);
		return false;
	}
	else
	{
		return true;
	}
}

int main()
{
#ifdef _MSC_VER
	WORD wVersion;
	WSADATA wsData;
	wVersion = MAKEWORD(1, 1);
	if (WSAStartup(wVersion, &wsData) != 0)
	{
		perror("windows network init error");
		return false;
	}

	int nOn = 1000;
#else
	struct timeval nOn = { 1,0 };
#endif 

	// 初始化Socket
	uint32_t dwSocketId = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (dwSocketId == -1)
	{
		return CloseSerSocket(dwSocketId, "init socket error, can not get file description");
	}

	// 設置地址重用
	if (setsockopt(dwSocketId, SOL_SOCKET, SO_REUSEADDR, (const char*)&nOn, sizeof(nOn)) == -1)
	{
		return CloseSerSocket(dwSocketId, "set socket error");
	}

	// 設置服務器信息
	sockaddr_in stServerAddr;
	sockaddr_in stClientAddr;
	stServerAddr.sin_family = AF_INET;
	stServerAddr.sin_port = htons(5050);
	stServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY就是inet_addr("0.0.0.0") 當服務器的監聽地址是INADDR_ANY時,就是全部的都監聽

	// 綁定socket
	if (bind(dwSocketId,(const sockaddr*)&stServerAddr,sizeof(stServerAddr)) == -1)
	{
		return CloseSerSocket(dwSocketId, "bind socket error");
	}

	// 監聽客戶端請求
	if (listen(dwSocketId, 100) == -1)
	{
		return CloseSerSocket(dwSocketId, "listen socket error");
	}

	socklen_t nCliSize = 0;
	uint32_t dwSocketFd = 0;
	char reveBuff[2048];
	char sendBuff[2048];
	while (true)
	{
		memset(reveBuff, '\0', sizeof(reveBuff));
		memset(sendBuff, '\0', sizeof(sendBuff));
		nCliSize = sizeof(stClientAddr);

		// 從已完成鏈接隊列頭返回一個已完成鏈接
		if ((dwSocketFd = accept(dwSocketId, (sockaddr*)&stClientAddr, &nCliSize)) == -1)
		{
			return CloseSerSocket(dwSocketId, "accept socket error");
		}

		// 接收客戶端包
		if (recv(dwSocketFd, reveBuff, 2048, 0) == -1)
		{
			return CloseSerSocket(dwSocketId, "recv package error");
		}

		sprintf(sendBuff, "i have receive the message:%s from you(%s)", reveBuff, inet_ntoa(stClientAddr.sin_addr));

		// 返回客戶包
		if (send(dwSocketFd, sendBuff, strlen(sendBuff), 0) == -1)
		{
			return CloseSerSocket(dwSocketId, "send package error");
		}
	}
	CloseSerSocket(dwSocketId, NULL);
    return 0;
}

 

TCP客戶端:僅限Windows端

// ClientMain.cpp : Windows Only
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")

bool CloseCliSocket(uint32_t dwSockId, const char* strError)
{
	closesocket(dwSockId);
	if (NULL != strError)
	{
		perror(strError);
		system("pause");
		return false;
	}
	else
	{
		return true;
	}
}

int main()
{
	WORD wVersion;
	WSADATA wsData;
	wVersion = MAKEWORD(1, 1);
	if (WSAStartup(wVersion,&wsData) != 0)
	{
		perror("windows network init error");
		return false;
	}

	// 初始化Socket
	uint32_t dwSocketId = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (dwSocketId == -1)
	{
		return CloseCliSocket(dwSocketId, "init socket error, can not get file description");
	}

	// 設置服務器信息
	sockaddr_in stServerAddr;
	stServerAddr.sin_family = AF_INET;
	stServerAddr.sin_port = htons(5050);
	stServerAddr.sin_addr.s_addr = inet_addr("192.168.1.5"); 

	if (connect(dwSocketId, (const sockaddr*)&stServerAddr, sizeof(stServerAddr)) == -1)
	{
		return CloseCliSocket(dwSocketId, "connect server error");
	}

	char delBuff[2048];
	sprintf(delBuff, "%s", "hello");

	// 發送包
	if (send(dwSocketId, delBuff, strlen(delBuff), 0) == -1)
	{
		return CloseCliSocket(dwSocketId, "send package error");
	}

	memset(delBuff, '\0', sizeof(delBuff));

	// 接收包
	if (recv(dwSocketId, delBuff, 2048, 0) == -1)
	{
		return CloseCliSocket(dwSocketId, "recv package error");
	}

	printf("%s\n", delBuff);
		
	CloseCliSocket(dwSocketId, NULL);
	system("pause");
	return 0;
}
相關文章
相關標籤/搜索