套接字編程 ---- TCP協議

一、套接字(socket)

    套接字socket: ip地址 + port端口號。在TCP/IP協議中,它唯一標識網絡通訊中的一個進程。

    在TCP協議中,建立連接的兩個進程各自有一個socket來標識,那麼這兩個socket組成的socketpair就唯一標識一個連接。

    socket本身有“插座”的意思,因此用來描述網絡連接的 一對一關係。


    TCP/IP協議規定,網絡數據流應採用 大端字節序,即 (內存)低地址高字節(數據)。

 

二、TCP_SOCKET 相關

TCP 協議  ----  基於 字節流   ---  SOCK_STREAM 

IPv4地址格式定義在netinet/in.h中,IPv4地址: sockaddr_in結構體,包括16位端口號和32位IP地址

struct sockaddr_in
 {
    uint8_t sin_len;
    sa_family_t sin_family;
    in_port_t sin_port;
    struct in_addr sin_addr;
    char sin_zero[8];
};


h表示host,n表示network,l表示32位長整數,s表示16位短整數。

wKiom1dBn4qzcaVWAACcBO7tGTg025.jpg

#include <sys/types.h>  

#include <sys/socket.h>

int socket(int domain, int type, int protocol); //創建套接字

    //domain: 底層通信所使用的協議   AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX 

    //type: 協議實現的方式  SOCK_STREAM 有序的、可靠的、雙向的和基於連接的字節流                      //protocol: 套接口所用的協議。前面兩個參數設定後,這兒可用0指定,表示缺省。

返回值:成功 new socket的描述符,失敗 -1


int bind(int sockfd, const struct sockaddr  *addr,socklen_t  addrlen);//綁定

int listen(int sockfd, int backlog); //監聽 

     // sockfd:已經建立的socket編號(描述符)   SOCK_STREAM     SOCK_DGRAM

     // backlog: 連接請求隊列的最大長度

返回值:成功 0,失敗 -1


int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//阻塞等待

返回值:成功  非負整數的accept socket的描述符,失敗 -1

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen); 

返回值:連接/綁定成功 0,失敗 -1


#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count); 

ssize_t read(int fd, void *buf, size_t count);


三、TCP協議 通訊流程

   1、服務器:調用socket()、bind()、listen() 完成初始化後,調用accept()阻塞等待,處於監聽端口的狀態。

      客戶端:調用socket()初始化後,調用connect()發出SYN段並阻塞等待服務器應答,服務器應答一個SYN-ACK段,客戶端收到後從connect()返回,同時應答一個ACK段,服務器收到後從accept()返回。

   2、數據傳輸的過程

    建立連接後,TCP協議提供全雙工的通信服務,但是一般的客戶端/服務器程序的流程是由客戶端主 動發起請求,服務器被動處理請求,一問一答的方式。因此,服務器從accept()返回後立刻調read(),socket就像讀管道一樣,如果沒有數據到達就阻塞等待,這時客戶端調用write()發送 請求給服務器,服務器收到後從read()返回,對客戶端的請求進行處理,在此期間客戶端調用read()阻塞等待服務器的應答,服務器調用write()將處理結果發回給客戶端,再次調用read()阻塞等待下一條請求,客戶端收到後從read()返回,發送下一條請求,如此循環下去。

    如果客戶端沒有更多的請求了,就調用close()關閉連接,就像寫端關閉的管道一樣,服務器的read()返回0,這樣服務器就知道客戶端關閉了連接,也調用close()關閉連接。注意,任何一方調用close()後,連接的兩個傳輸方向都關閉,不能再發送數據了。如果一方調用shutdown()則連接處於半關閉狀態,仍可接收對方發來的數據。

四、TCP socket API實例

//tcp_server.cpp

#include<iostream>
using namespace std;

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

const int g_backlog=5;


void usage(string _proc)
{
	cout<<"Usage:"<<_proc<<"[ip][port]"<<endl;
}
static int startup(const string &ip,const int &port) //服務器初始化
{
	int sock=socket(AF_INET,SOCK_STREAM,0);//創建套接字
	if(sock < 0)
	{
		cout<<strerror(errno)<<endl;
		exit(1);
	}
	struct sockaddr_in local;//填充本地信息
	local.sin_family=AF_INET;
	local.sin_port=htons(port);
	local.sin_addr.s_addr=inet_addr(ip.c_str());
	
	if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)//綁定
	{
		cout<<strerror(errno)<<endl;
		exit(2);
	}	
	if(listen(sock,g_backlog) < 0)//監聽
	{
		cout<<strerror(errno)<<endl;
		exit(3);
	}
	return sock;
}
void *thread_run(void *arg)
{
	char buf[1024];
	int sock=(int)arg;
	while(1)
	{
		memset(buf,'\0',sizeof(buf));
		ssize_t _size = read(sock,buf,sizeof(buf)-1);
		if(_size > 0)//read success
		{
			buf[_size] = '\0';
		}
		else if(_size == 0)//close connection
		{
			cout<<"client  close..."<<endl;
			break;
		}
		else
		{
			cout<<strerror(errno)<<endl;
		}
		cout<<"Client# "<<buf<<endl;
	}
	close(sock);
	return NULL;
}

int main(int argc,char *argv[])
{
	if(argc != 3)
	{
		usage(argv[0]);
		exit(1);
	}
	string ip = argv[1];
	int port = atoi(argv[2]);
	int listen_sock=startup(ip,port);

	struct sockaddr_in client;
	socklen_t len=sizeof(client);
	client.sin_family = AF_INET;
	client.sin_port = htons(port);
	client.sin_addr.s_addr = inet_addr(ip.c_str());

	while(1)
	{
		int new_sock=accept(listen_sock,(struct sockaddr*)&client,&len);//阻塞等待
		if(new_sock < 0)
		{
			cout<<strerror(errno)<<endl;
			continue;
		}
		cout<<"Get a connect..."<<"sock: "<<new_sock<<"ip: "<<inet_ntoa(client.sin_addr)\
			<<"port: "<<ntohs(client.sin_port);
	}
#ifdef _V1_
	char buf[1024];
	while(1)
	{
		ssize_t _size = read(new_sock,buf,sizeof(buf)-1);
		if(_size > 0)//read success
		{
			buf[_size] = '\0';
		}
		else if(_size == 0)//client close 
		{}
		else
		{
			cout<<strerror(errno)<<endl;
		}
		cout<<"Client#  "<<buf<<endl;
	}
#elif _V2_
	cout<<"V2"<<endl;
	pid_t id=fork();
	if(id < 0)
	{
		cout<<strerror(errno)<<endl;
		exit(1);
	}
	else if(id == 0)//child
	{
		string _client=inet_ntoa(client.sin_addr);
		close(listen_sock);
		char buf[1024];
		while(1)
		{
			memset(buf,'\0',sizeof(buf));
			ssize_t _size = read(new_sock,buf,sizeof(buf)-1);
			if(_size > 0)//read success
			{
				buf[_size] = '\0';
			}
			else if(_size == 0)//client close 
			{
				cout<<_client<<"close..."<<endl;
				break;
			}
			else
			{
				cout<<strerror(errno)<<endl;
			}
			cout<<_client<<"# "<<buf<<endl;
		}
		close(new_sock);
		exit(0);
	}
	else //father
	{
		close(new_sock);
	}
#elif _V3_
	cout<<"V3"<<endl;
	pthread_t tid;
	pthread_create(&tid,NULL,thread_run,(void*)argc);
	pthread_detach(tid);
#else
	cout<<"defauult"<<endl;
#endif
	
	return 0;
}

//tcp_client.cpp

#include<iostream>
using namespace std;
#include<string.h>
#include<stdlib.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
	
void usage(string _proc)
{
	cout<<_proc<<"[remote ip][remote port]"<<endl;
}
int main(int argc,char *argv[]) //tcp_client.cpp
{
	if(argc != 3)
	{
		usage(argv[0]);
		exit(1);
	}
	string r_ip = argv[1];
	int r_port = atoi(argv[2]);
	int sock = socket(AF_INET,SOCK_STREAM,0);
	if(sock < 0)
	{
		cout<<strerror(errno)<<endl;
		exit(1);
	}
	struct sockaddr_in remote;
	remote.sin_family = AF_INET;
	remote.sin_port = htons(r_port);
	remote.sin_addr.s_addr = inet_addr(r_ip.c_str());

	int ret = connect(sock,(struct sockaddr*)&remote,sizeof(remote));
	if(ret < 0)
	{
		cout<<strerror(errno)<<endl;	
	}
	string msg;
	while(1)
	{
		cout<<"Please Enter: ";
		cin>>msg;
		write(sock,msg.c_str(),msg.size());
	}
	return 0;
}

//Makefile

.PHONY:all
all:tcp_server tcp_client

tcp_server:tcp_server.cpp
	g++ -o $@ $^ -lpthread -D_V3_
tcp_client:tcp_client.cpp
	g++ -o $@ $^

.PHONY:clean
clean:
	rm -f tcp_server tcp_client


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