【TCP通信】原理詳解與編程實現(二)


上一篇文章【TCP通信】原理詳解與編程實現(一)總結了TCP通信的基本原理。本篇文章是編碼實現部分
TCP把連接作爲最基本的抽象單元,每條TCP連接有兩個端點,TCP連接的端點即套接字
套接字socket = (IP地址+端口號)
可以認爲,套接字就是網絡編程的接口,是連接網絡的一種工具。

本文先介紹linux下套接字相關函數,最後編碼實現一個簡單的TCP通信demo。windows下的方法相近。

1.套接字相關

1.1 socket() 函數

socket() 函數創建套接字,原型:

#include <sys/socket.h> 
int socket(int domain, int type, int protocol); 

1)domain 是協議族,也就是 IP 地址類型,常用的有 AF_INET(IPv4) 和 AF_INET6(IPv6)
2)type 爲數據傳輸方式,常用的有 SOCK_STREAM (面向連接的數據傳輸方式)和 SOCK_DGRAM(無連接的數據傳輸方式)
3)protocol 表示傳輸協議,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分別表示 TCP 傳輸協議和 UDP 傳輸協議
舉例

int tcp_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
int udp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); 

1.2 bind() 函數

bind() 函數.服務器端 將套接字與特定的IP地址和端口綁定

  int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);  

成功返回0,失敗返回-1。
sockfd 要分配地址信息(IP地址和端口號)的套接字文件描述符
myaddr 存有地址信息的結構體變量地址值
addrlen 第二個結構體變量的長度
其中的結構體原型 :

struct sockaddr_in 
{ 
  sa_family_t    sin_family;//地址族 
  uint16_t       sin_port;//16位tcp/udp端口號,以網絡字節序保存 
  struct in_addr sin_addr;//32位IP地址,以網絡字節序保存 
  char           sin_zero[8];//不使用,必需爲0, 
                                   爲使結構體sockaddr_in的大小與sockaddr結構體保持一致而插入的成員 
};

該結構體的初始化舉例:

struct  sockaddr_in   addr; 
char * serv_ip = "211.217.168.13"; //聲明IP地址字符串
char * serv_port = "9190";  //聲明端口號字符串
memset(&addr, 0, sizeof(addr)); //結構體變量addr的所有成員置零
addr.sin_family = AF_INET; //指定地址族
addr.sin_addr.s_addr = inet_addr(serv_ip); //基於字符串的IP地址初始化 
addr.sin_port = htons(atoi(serv_port)); //基於字符串的端口號初始化

1.3 connect() 函數

connect() 函數由客戶端用來建立連接

int connect(int sockfd,  struct sockaddr *serv_addr, int addrlen);

1.4 listen()函數

listen()函數讓套接字進入被動監聽狀態

int listen(int sockfd, int backlog);

1.5 accept()函數

accept() 函數當套接字處於監聽狀態時,可以通過 accept() 函數來接收客戶端請求

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

addr 保存發起連接請求的客戶端地址信息的變量地址值 .
函數調用成功時,accept函數內部將產生用於數據I/O的套接字,並返回其文件描述符,套接字自動創建並自動與發起連接的客戶端建立連接

1.6 send()與recv()函數

send() 函數發送數據,recv() 函數接受數據

int send(int sockfd,  char *buf, int len, int flags);

int recv(int sockfd, char *buf, int len, int flags);

buf 爲要發送的數據的緩衝區地址,len 爲要發送的數據的字節數,flags 爲發送數據時的選項。

1.7 closesocket()函數關閉套接字,原型爲:

void closesocket(int sockfd);

2 服務端c/c++代碼

tcp_server.cpp

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

#define MAXDATASIZE 100  

#define PORT 8087
#define KEY 123
#define SIZE 1024

int main()
{

    char buf[100];
    memset(buf,0,100);

    int server_sockfd,client_sockfd;
    socklen_t server_len,client_len;

    struct  sockaddr_in server_sockaddr,client_sockaddr;

    /*建立服務端套接字*/
    server_sockfd = socket(AF_INET,SOCK_STREAM,0);
    
    server_sockaddr.sin_family = AF_INET;		//ipv4
    server_sockaddr.sin_port = htons(PORT);   //端口 #define PORT 8087
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); //任意地址

    server_len = sizeof(server_sockaddr);
    
    int on;
    setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR,&on,sizeof(on));
    
    /*bind 將套接字與IP地址和端口綁定*/
    if(bind(server_sockfd, (struct sockaddr*)&server_sockaddr, server_len)==-1){
        printf("bind error");
        exit(1);
    }

	//監聽
    if(listen(server_sockfd, 5)==-1){
        printf("listen error");
        exit(1);
    }

    client_len = sizeof(client_sockaddr);

    pid_t ppid,pid;

    while(1) {
	printf("start\n");

		//當套接字處於監聽狀態時,可以通過 accept() 函數來接收客戶端請求。
        if((client_sockfd = accept(server_sockfd, (struct sockaddr*)&client_sockaddr, &client_len)) == -1){
            printf("connect error");
            exit(1);
        } else 
	{
            printf("create connection successfully\n");
	    while(1){
	    	//發送數據
	    	int error = send(client_sockfd, "I am the server", strlen("I am the server"), 0);
		if(error > 0 ){
		    printf("send    : I am the server\n");
		}
        
        //接收數據
		int numbytes = recv(client_sockfd, buf, MAXDATASIZE, 0);
		if (numbytes > 0) { 
          	   buf[numbytes] = '\0'; 
          	   printf("Received: %s\n",buf);
		 }
	
	   	sleep(1);
	    }
            
        }
	printf("next\n");
        
 } 
    return 0;
}

3 客戶端c/c++代碼

tcp_client.cpp

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

#define SERVER_PORT 8087
#define MAXDATASIZE 100  
#define SERVER_IP "127.0.0.1"

int main() { 
    int sockfd, numbytes; 
    char buf[MAXDATASIZE]; 
    struct sockaddr_in server_addr; 

    printf("\n client initialization \n"); 
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { 
        perror("socket"); 
        exit(1); 
    }

    server_addr.sin_family = AF_INET; 
    server_addr.sin_port = htons(SERVER_PORT); 
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); 
    bzero(&(server_addr.sin_zero),sizeof(server_addr.sin_zero)); 

	//連接
    if (connect(sockfd, (struct sockaddr *)&server_addr,sizeof(struct sockaddr_in)) == -1){
         perror("connect error"); 
         exit(1);
     } 
    
     while(1) { 
         bzero(buf,MAXDATASIZE); 
         //接收消息
         if ((numbytes = recv(sockfd, buf, MAXDATASIZE, 0)) == -1){  
             perror("recv"); 
             exit(1);
         } else if (numbytes > 0) {  //若成功接收
             int len, bytes_sent;
             buf[numbytes] = '\0'; 
            printf("Received:%s\n",buf);
            
            char msg[100];
	   	    strcpy(msg,"i am a client");
            len = strlen(msg); 
            
            //sent to the server
            if(send(sockfd, msg,len,0) == -1){ 
                perror("send error"); 
            }else{
           		 printf("Send    :i am a client\n"); 
            }
        } 
        else { 
            printf("soket end!\n"); 
            break;
        } 
	sleep(1);
    }  
        close(sockfd); 
        return 0;
    }

4 demo測試

將以上兩個cpp文件放在一個文件夾下,分別編譯

g++ -O3 tcp_client.cpp -o client
g++ -O3 tcp_server.cpp -o server

在兩個終端分別運行:

./server
./client

測試結果:
在這裏插入圖片描述可見完成了雙向通信的demo。

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