[Linux]-TCP

目录

[TCP编程概述]

1.TCP通信流程

[TCP客户端]

1.TCP套接字的创建

1.1定义一个套接字

1.2使用connect建立和服务器套接字连接

1.3定义结构体后使用conne建立连接

1.4send发送数据

1.5recv接收信息(默认阻塞)

2.TCP客户端收发数据

[TCP服务器]

1.作为服务器的条件

2.监听套接字

3.accept提取客户端(默认阻塞)

4.编写一个TCP服务器

[close关闭套接字]

1.当客户端使用close

2.当服务器使用close

[三次握手四次挥手]

[基于多进程的并发服务器]

多进程并发服务器实现

[基于多线程的并发服务器]

多线程并发服务器实现


[TCP编程概述]

1.TCP通信流程

客户端:主动连接服务器,和服务器进行通信

服务器:被动连接客户端,启动新的进程或者线程,通过新的端口号服务客户端(并发服务器)


[TCP客户端]

1.TCP套接字的创建

1.1定义一个套接字
int socket_fd = socket(AF_INET,SOCK_STREAM,0);
1.2使用connect建立和服务器套接字连接
#include <sys/types.h>          
#include <sys/socket.h>

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

功能

        使当前套接字能够建立和服务器的连接(需要知道服务器的IP 端口)

参数

        sockfd:想要连接的套接字

        addr:目标服务器的地址结构体地址

        addrlen:目标结构体地址大小

返回值

        成功:0

        失败:-1,并且会设置错误值

1.3定义结构体后使用conne建立连接

这里的客户端并没有绑定,所以端口是随机分配的

int socket_fd = socket(AF_INET,SOCK_STREAM,0);

struct sockaddr_in ser_addr;
//定义在栈区,清零结构体
bzero(&ser_addr, sizeof(ser_addr));
//设置地址族为IPv4
ser_addr.sin_family = AF_INET;
//设置端口的值,主机字节序转网络字节序
ser_addr.sin_port = htons (8000 );
//设置目的主机的IP,点分十进制串转32无符号
ser_addr.sin_addr.s_addr = inet_addr( "xx.xx.xx.xxx");
//建立连接
connect(sockfd,(struct sockaddr *)&ser_addr, sizeof(ser_addr));
1.4send发送数据
#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

功能

        向建立好连接的服务器发送指定数据

参数

        sockfd:套接字

        buf:发送数据缓冲区

        len:发送数据长度

        flags:默认为0

返回值

        成功:返回发送字节数

        失败:-1

TCP不能想UDP一样发送0长度报文,因为发送0长度报文是断开连接的意思

1.5recv接收信息(默认阻塞)
#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

功能

        建立连接后,从服务器接受信息

参数

        sockfd:套接字

        buf:接收数据缓冲区

        len:接受数据长度

        flags:默认0

返回值

        成功:接收到的字节个数

        失败:-1

recv如果接受到0长度报文,则对方已经断开连接


2.TCP客户端收发数据

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    //创建TCP套接字
    int socket_fd = socket(AF_INET,SOCK_STREAM,0);

    //创建目标地址结构体
    struct sockaddr_in dst_addr;
    bzero(&dst_addr,sizeof(dst_addr));
    dst_addr.sin_family = AF_INET;
    dst_addr.sin_port = htons(8000);
    //inet_addr只支持IPv4
    //inet_pton支持IPv4和IPv6

    //服务器的IP地址
    dst_addr.sin_addr.s_addr = inet_addr("XXX.XXX.XXX.XXX");

    //建立客户端和服务器的连接
    connect(socket_fd,(struct sockaddr *)&dst_addr,sizeof(dst_addr));

    //通过TCP给服务器发送信息
    int send_len = send(socket_fd,"hello tcp",strlen("hello tcp"),0);
    printf("信息发送成功,发送了%d字节\n",send_len);

    //接受信息(带阻塞)
    char buf[1000] = "";
    int recv_len = recv(socket_fd,buf,sizeof(buf),0);
    printf("成功接收到如下信息%s,接收字节为%d",buf,recv_len);

    close(socket_fd);
    return 0;
}

[TCP服务器]

1.作为服务器的条件

如果想要编写一个服务器,那么应该遵循如下步骤,首先,需要创建一个套接字,然后使用bind对其进行IP端口的绑定,因为作为服务器,如果没有确切的IP和端口,其他客户端是无法连接你的。当绑定完毕后,使用accept对客户端连接列表中的客户端进行提取,然后创建一个线程或者进程与客户端进行通信。 


2.监听套接字

服务器需要不断循环监听是否有新的客户端连接进来,这时候就需要一个监听套接字来完成这个任务。使用listen可以把一个套接字变为监听套接字


3.accept提取客户端(默认阻塞)

 当一个客户端连接了一个服务器后,会在连接列表中的未完成部分,当完成了三次握手之后,accept可以成功的把客户端提取出来,然后创建一个临时的套接字与之进行通信。

#include <sys/types.h>         
#include <sys/socket.h>

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

功能

        从连接列表中提取完成三次握手的客户端

参数

        sockfd:监听套接字

        addr:客户端地址信息

        addrlen:地址结构体长度

返回值

        成功:与客户端进行临时通信的套接字,代表服务器真正和客户端的连接端点

        失败:-1


4.编写一个TCP服务器

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

int main(int argc, char const *argv[])
{
    //创建监听套接字
    int listen_socket = socket(AF_INET,SOCK_STREAM,0);

    //创建地址结构体
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(1145);
    //客户端IP
    server_addr.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx");
    //绑定
    int bind_res = bind(listen_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
    if( bind_res < 0 )
    {
        perror("bind:");
    }
    //设置套接字为监听套接字
    listen(listen_socket,10);

    //提取连接此服务器的地址
    struct sockaddr_in client_addr;
    socklen_t client_addr_len;
    bzero(&client_addr,sizeof(client_addr));

    //提取在连接列标中完成三次握手的ip
    char clinet_ip[16] = "";
    int client_socket = accept(listen_socket,(struct sockaddr *)&client_addr,&client_addr_len);
    printf("客户端%s端口为%hu连接成功,使用套接字%hu与之通信\n",\
            inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,clinet_ip,16),\
            ntohs(client_addr.sin_port),ntohs(client_socket));

    //客户端接受数据
    char recv_data[128] = "";
    recv(client_socket,recv_data,sizeof(recv_data),0);
    printf("服务器接收到数据%s\n",recv_data);

    //服务器给客户端通过临时端口发送数据
    send(client_socket,"hello client",strlen("hello client"),0);
    printf("发送数据成功!!\n");


    while(1);
    //关闭监听套接字
    close(listen_socket);
    return 0;
}

[close关闭套接字]

1.当客户端使用close

客户端使用close关闭套接字,会断开当前连接,导致服务器收到一个0长度报文(可以作为服务器不在继续接受数据的依据)

2.当服务器使用close

如果服务器close了监听的套接字,那么该服务器将无法在继续监听新的客户端的到来,但是不会影响现有的连接

如果服务器close了通信的套接字,那么会断开与客户端的连接,但是不会影响监听


[三次握手四次挥手]

详见博主的另外一篇文章

https://blog.csdn.net/m0_72372635/article/details/131617634?spm=1001.2014.3001.5501


[基于多进程的并发服务器]

int main(int argc, char *argv [])
{

    //创建套接字sockfd
    //绑定(bind)套接字
    //sockfd监听(listen)套接字sockfd

    while(1)
    {
        //阻塞,直到有客户端完成三次握手完成连接
        //提取临时通信的套接字
        int temp_socket = accept(.....);

        pid_t pid = fork();
        //子进程
        if( 0 == pid )
        {
            //关闭监听套接字
            close(sockfd);          
            //实现函数
            fun();
            //关闭临时套接字
            close(temp_socket);
            exit(-1);
        }
        else if( pid > 0 )
        {
            //关闭临时套接字
            close(temp_socket);
        }
    }
    close(sockfd);
    return 0;  
}

多进程并发服务器实现

这里实现了一个基于多进程的高并发服务器,当客户端连接的时候,会打印IP地址以及端口,并且客户端发送的内容服务器会提取之后发送回去(可能回收机制有点缺陷)

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


void Recv_ClientMsg(int socket)
{
    while(1)
    {
        char recv_data[128] = "";
        //接受信息
        recv(socket,recv_data,128,0);
        send(socket,recv_data,128,0);
        if( 0 == strcmp("exit connect",recv_data) )
            break;
    }
}

int main(int argc, char const *argv[])
{
    
    //创建套接字
    int listen_socket = socket(AF_INET,SOCK_STREAM,0);
    //绑定
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8000);
    server_addr.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx");
    int bind_res = bind(listen_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
    if( bind_res != 0 )
    {
        perror("bind:");
    }

    //设定服务器的套接字为监听套接字
    listen(listen_socket,10);

    //接受来访客户端IP 端口
    struct sockaddr_in client_addr;
    socklen_t client_addr_len;
    while(1)
    {
        
        int client_socket = accept(listen_socket,(struct sockaddr *)&client_addr,&client_addr_len);
       
        //每次有客户端连接进入,都创建进程
        pid_t pid = fork();
        //子进程
        if( 0 == pid )
        {
            //查看哪个IP到来
            char ip[16] = "";
            inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip,16);
            printf("%s进入连接,端口%hu\n",ip,ntohs(client_addr.sin_port));
            
            //子进程中监听套接字无用
            close(listen_socket);
            Recv_ClientMsg(client_socket);
            printf("%s断开连接,端口%hu\n",ip,ntohs(client_addr.sin_port));
            
            //关闭临时套接字
            close(client_socket);
            _exit(-1);
        }
        //父进程
        else if( pid > 0 )
        {
            //父进程中临时套接字无用
            close(client_socket);
        }
    }
    close(listen_socket);

    return 0;
}

[基于多线程的并发服务器]

int main(int argc, char *argv [])
{

    //创建套接字sockfd
    //绑定(bind)套接字
    //sockfd监听(listen)套接字sockfd

    while(1)
    {
        //阻塞,直到有客户端完成三次握手完成连接
        //提取临时通信的套接字
        int temp_socket = accept(.....);

        //每次提取到新的客户端,创建线程
        pthread_t tid;
        pthread_create(&tid,NULL,client_fun,temp_socket );
        //分离线程
        pthread_detach(tid);
    
    }
    close(sockfd);
    return 0;  
}

void* client_fun(void* arg)
{
    //接受传过来的套接字
    int temp_socket = (int)arg;
    
    //完成任务

    //关闭套接字
    close(temp_socket );

    return NULL;
}

多线程并发服务器实现

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

typedef struct 
{
    int client_socket;
    struct sockaddr_in client_addr;
}ClientMsg;

void* client_pthread_fun(void* arg)
{

    ClientMsg client_msg = *(ClientMsg*)arg;
    char ip[16] = "";
    char recv_data[128] = "";

    inet_ntop(AF_INET,&client_msg.client_addr.sin_addr.s_addr,ip,16);
    printf("%s进入连接,端口%hu\n",ip,ntohs(client_msg.client_addr.sin_port));

    while(1)
    {
        int len = recv(client_msg.client_socket,recv_data,sizeof(recv_data),0);
        //判断退出条件
        if( len <= 0 )
        {
            printf("%s退出连接,端口%hu\n",ip,ntohs(client_msg.client_addr.sin_port));
            close(client_msg.client_socket);
            break;
        }
        send(client_msg.client_socket,recv_data,strlen(recv_data),0);
        printf("往%s端口%hu发送了%s\n",ip,ntohs(client_msg.client_addr.sin_port),recv_data);
    }
 
    pthread_exit(NULL);
    return NULL;
}



int main(int argc, char const *argv[])
{
    //创建套接字
    int listen_socket = socket(AF_INET,SOCK_STREAM,0);

    //设置套接字允许端口复用
    int option = 1;
    setsockopt(listen_socket,SOL_SOCKET,SO_REUSEADDR,&option,sizeof(option));

    //绑定套接字
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8000);
    server_addr.sin_addr.s_addr = inet_addr("xxx.xxx.xxx.xxx");
    int bind_res = bind(listen_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
    if( bind_res != 0 )
    {
        perror("bind");
        close(listen_socket);
        return 0;
    }

    //设置为监听套接字
    listen(listen_socket,10);

    //创建客户端地址接受结构体
    struct sockaddr_in client_addr;
    socklen_t client_addr_len;

    //创建结构体可以传参到线程
    ClientMsg client_msg;
    
    //不断循环提取完成三次握手的客户端
    while(1)
    {
        //提取客户端
        int connect_socket = accept(listen_socket,(struct sockaddr *)&client_addr,&client_addr_len);
       
        //查看哪个IP到来
        client_msg.client_socket = connect_socket; 
        client_msg.client_addr.sin_port = ntohs(client_addr.sin_port);
        client_msg.client_addr.sin_addr.s_addr = client_addr.sin_addr.s_addr;
              
        //创建线程
        pthread_t client_pthread;
        pthread_create(&client_pthread,NULL,client_pthread_fun,&client_msg);
        //设置线程分离
        pthread_detach(client_pthread);

    }

    close(listen_socket);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_72372635/article/details/131644734
今日推荐