TCP编程

1.流程图

2.TCP编程API

  • socket函数
#include <sys/types.h>       
#include <sys/socket.h>

/*
功能:创建套接字
参数:
参数1:地址族(Address Family),即IP地址类型,常用有:
      AF_INET(IPV4类型),AF_INET6(IPV6类型)
      AF_UNIX, AF_LOCAL(本地进程通信)
      AF_NETLINK(内核与用户空间的通信,用于设备驱动)
      AF_PACKET(原始套接字)
参数2:套接字类型,常用有:
     SOCK_STREAM(流式套接字,对应TCP)
     SOCK_DGRAM(数据报套接字,对应UDP)
     SOCK_RAW(原始套接字)
参数3:TCP与UDP编程时候填0,原始套接字时候需填充
返回值:成功返回一个文件描述符,失败返回-1
*/
int socket(int domain, int type, int protocol);
  • bind函数
#include <sys/types.h>
#include <sys/socket.h>

/*
功能:将一本地地址与一套接口捆绑
参数:
参数1:通过socket函数获得的文件描述符
参数2:结构体地址,基于Internet通信时候填sockaddr_in类型结构体,需要强制转换为sockaddr类型
参数3:结构体长度
返回值:成功返回0,失败返回-1
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

struct sockaddr结构体内部为:

struct sockaddr
{
  sa_family_t sa_family;/*2字节*/
  char        sa_data[14];/*14字节*/
};

基于Internet通信时候参数2应该填充的结构体:sockaddr_in结构体,具体信息可查看man手册man 7 ip或者man 7 ipv6:

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>

struct sockaddr_in 
{
  sa_family_t    sin_family; /* 2字节,地址族 */
  in_port_t      sin_port;   /* 2字节,基于网络字节序的端口号*/
  struct in_addr sin_addr;   /* internet address */
  unsigned char sin_zer[32]; /*填充字节,一般需要清零*/
};
struct in_addr
{
  uint32_t       s_addr;     /*32位网络字节序IP地址 */
};
  • listen函数
#include <sys/types.h>      
#include <sys/socket.h>

/*
功能:把一个未连接的套接字转换成一个被动套接字,指示内核应该接受指向该套接字的连接请求
参数:
参数1:通过socket函数获得的文件描述符
参数2:同时允许几路客户端和服务器进行正在连接的过程(正在3次握手),一般写5,ARM最大为8
返回值:成功返回0,失败返回-1
*/
int listen(int sockfd, int backlog);

注意:内核中服务器的套接字fd会维护2个链表,一个是正在进行3次握手的客户端链表(数量 = 2*backlog+1),一个是已经建立好连接的客户端链表(已经完成了3次握手分配好了新的文件描述符)

  • accept函数,man 2 accept查看帮助文档
#include <sys/types.h>
#include <sys/socket.h>

/*
功能:阻塞等待客户端的连接请求
参数:
参数1:通过socket函数获得的文件描述符
参数2:指向sockaddr_in结构体的指针,即保留建立连接的客户端的信息,包含客户端IP地址与端口号
参数3:sizeof(struct sockaddr_in)
返回值:成功返回建立了连接的套接字文件描述符,失败返回-1
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

connect函数

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

/*
功能:
参数:连接服务器
参数1:文件描述符
参数2:同bind函数参数
参数3:参数2长度
返回值:成功返回0,失败返回-1
*/
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

send函数

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

/*
功能:发送数据
参数:
参数1:文件描述符
参数2:发送缓冲区首地址
参数3:发送的字节数
参数4:发送方式,通常为0(与write作用相同)
      MSG_DONTWAIT(非阻塞发送)
      MSG_OOB(用于发送TCP类型带外数据)
      
返回值:成功返回实际发送的字节数,失败返回-1
*/
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

recv函数

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

/*
功能:接收数据
参数:
参数1:文件描述符
参数2:接收缓冲区首地址
参数3:接收的字节数
参数4:接收方式,通常为0(与read作用相同)
      MSG_DONTWAIT(非阻塞接收)
      MSG_OOB(用于读取TCP类型带外数据)
      MSG_PEEK(用于预先读取数据,判断数据是否完整,该操作只是读取内核缓冲区中数据,但
              缓冲区内数据并没消失)
返回值:成功返回实际接收的字节数,失败返回-1
*/
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

3.示例

3.1编写程序基于C/S模式,实现客户端与服务器的通信

  • 头文件
#ifndef __TCPC1_H__
#define __TCPC1_H__
/*网络相关头文件 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

#include <fcntl.h>

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>

#include <errno.h>

#define SERV_PORT 5001
#define SERV "0.0.0.0"
#define SERV_IP_ADDR "47.105.70.108"
#define BACKLOG 5
#define QUIT_STR "quit\n"

#endif
  • 服务器端程序
#include "TCPC1.h"

int main(void)
{
    char buf[BUFSIZ];
    int fd = -1;
    int newfd = -1;
    int ret = -1;/*read函数返回值 */
    struct sockaddr_in sin;
    /*创建socket */
    if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket");
        exit(-1);
    }
    /*结构体成员清零 */
    bzero(&sin, sizeof(sin));
    /*填充结构体 */
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SERV_PORT);/*网络字节序端口号 */
#if 0
    sin.sin_addr.s_addr = inet_addr(SERV);
#else
    if(inet_pton(AF_INET, SERV, (void *)&sin.sin_addr) != 1)
    {
        perror("inet_pton");
        exit(-1);
    }
#endif
    /*绑定socket */
    if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
        perror("bind");
        exit(-1);
    }
    /*主动套接字转被动套接字 */
    if(listen(fd, BACKLOG) < 0)
    {
        perror("listen");
        exit(-1);
    }
    /*阻塞等待客户端连接请求 */
    newfd = accept(fd, NULL, NULL);
    if(newfd < 0)
    {
        perror("accept");
        exit(-1);
    }
    while(1)
    {
        /*与newfd进行读写 */
        bzero(buf, BUFSIZ);
        /*判断读函数是否出错 */
        do
        {
            ret = read(newfd, buf, BUFSIZ - 1);
        } while (ret < 0 && EINTR == errno);
        if(ret < 0)
        {
            perror("read");
            exit(-1);
        }
        if(0 == ret)
        {
            break;
        }
        printf("Service receive data: %s\n", buf);
        if(strcmp(buf, QUIT_STR) == 0)
        {
            printf("Client is exting...\n");
            break;
        }
    }
    close(newfd);
    close(fd);
    return 0;
}
  • 客户端程序
#include "TCPC1.h"

int main(void)
{
    int fd = -1;
    struct sockaddr_in sin;
    char buff[BUFSIZ];
    int ret = -1/*write函数返回值 */;
    /*创建socket */
    if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket");
        exit(-1);
    }
    /*结构体成员清零 */
    bzero(&sin, sizeof(sin));
    /*填充结构体 */
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SERV_PORT);/*网络字节序端口号 */
#if 0
    sin.sin_addr.s_addr = inet_addr(SERV_IP_ADDR);
#else
    if(inet_pton(AF_INET, SERV_IP_ADDR, (void *)&sin.sin_addr.s_addr) != 1)
    {
        perror("inet_pton");
        exit(-1);
    }
#endif
    /*连接服务器 */
    if(connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
        perror("connect");
        exit(-1);
    }
    /*读写数据 */
    while(1)
    {
        bzero(buff, BUFSIZ);
        if(fgets(buff, BUFSIZ - 1, stdin) == NULL)
        {
            continue;
        }
        write(fd, buff, strlen(buff));
        if(strcmp(buff, QUIT_STR) == 0)
        {
            printf("Client is exting...\n");
            break;
        }
    }
    /*关闭套接字 */
    close(fd);
    return 0;
}
  • 效果:将编译生成的server可执行文件放到阿里云服务器上允许,本地允许客户端程序,结果如下:
    云端:
[root@HQ InternetTest] ./server 
Service receive data: a

Service receive data: quit

Client is exting...

本地端:

hanqi@hanqi-PC:~/C/Internet$ ./client 
a
quit
Client is exting...
  • 注意:
    在服务器端的IP地址应该写为sin.sin_addr.s_addr = htonl(INADDR_ANY),这样可以让任意客户端访问服务器
  • 不足:
    在上述程序中存在缺陷,可以看到,在服务器端存在两个地方的阻塞,一个是accept()函数会阻塞等待客户端连接,一个是read()函数也会阻塞,并且其位于while(1)之下,所以会一直等待客户端1的输入数据,因此此服务器只能响应一次客户端的连接,即只能连接一个客户端,与实际服务器情况不符,故需改进.
  • 改进方案:
    1.使用多路复用方案解决2.创建多进程/线程为客户端服务

3.2 3.1PLUS

  • TCP多进程并发服务器
#include "common.h"

void client_fork(void *arg);
/*子进程结束信号处理函数 */
void sig_child_handle(int signo)
{
    /*回收子进程 */
    if(SIGCHLD == signo)
    {
        waitpid(-1, NULL, WNOHANG);
    }
}
int main(void)
{
    pid_t pid;
    char clientIPV4[16];/*客户端IP地址 */
    
    int fd = -1;
    int newfd = -1;
    
    int b_reuse = 1;
    struct sockaddr_in sin;
    struct sockaddr_in client_message;
    socklen_t client_message_len = sizeof(client_message);
    signal(SIGCHLD, sig_child_handle);
    /*创建socket */
    if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket");
        exit(-1);
    }
    /*允许绑定地址快速重用 */
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int));
    /*结构体成员清零 */
    bzero(&sin, sizeof(sin));
    /*填充结构体 */
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SERV_PORT);/*网络字节序端口号 */
    /*让服务器能收到任意客户端数据 */
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    /*绑定socket */
    if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
        perror("bind");
        exit(-1);
    }
    /*主动套接字转被动套接字 */
    if(listen(fd, BACKLOG) < 0)
    {
        perror("listen");
        exit(-1);
    }
    puts("Server staring ok");
    while(1)
    {
        /*阻塞等待客户端连接请求并保存客户端信息 */
        if((newfd = accept(fd, (struct sockaddr *)&client_message, &client_message_len)) < 0)
        {
            perror("accept");
            break;
        }
        /*创建子进程,用于处理客户端数据 */
        if((pid = fork()) < 0)
        {
            perror("fork");
            break;
        }
        /*子进程 */
        if(0 == pid)
        {
            close(fd);
            /*客户端IP地址网络字节序转本地字节序 */
            if(!inet_ntop(AF_INET, (void *)&client_message.sin_addr.s_addr, clientIPV4, sizeof(client_message)))
            {
                perror("inet_ntop");
                exit(-1);
            }
            printf("client(%s: %d) is connected!\n", clientIPV4, ntohs(client_message.sin_port));
            client_fork(&newfd);
            return 0;
        }
        /*父进程 */
        else
        {
            close(newfd);
        }
    }
    close(fd);
    return 0;
}
void client_fork(void * arg)
{
    char buf[BUFSIZ];
    int ret = -1;/*read函数返回值 */
    int newfd = *(int *)arg;
    printf("process newfd = %d\n", newfd);
    while(1)
    {
        /*与newfd进行读写 */
        bzero(buf, BUFSIZ);
        /*判断读函数是否出错 */
        do
        {
            ret = read(newfd, buf, BUFSIZ - 1);
        } while (ret < 0 && EINTR == errno);
        if(ret < 0)
        {
            perror("read");
            exit(-1);
        }
        if(0 == ret)
        {
            break;
        }
        printf("Service receive data: %s\n", buf);
        if(strcmp(buf, QUIT_STR) == 0)
        {
            printf("Client(fd = %d) is exting...\n", newfd);
            break;
        }
    }
    close(newfd);
}
  • TCP多线程并发服务器
#include "common.h"

void *client_thread(void *arg);
int main(void)
{
    pthread_t a_thread;
    char clientIPV4[16];/*客户端IP地址 */
    
    int fd = -1;
    int newfd = -1;
    
    int b_reuse = 1;
    struct sockaddr_in sin;
    struct sockaddr_in client_message;
    socklen_t client_message_len = sizeof(client_message);
    /*创建socket */
    if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        perror("socket");
        exit(-1);
    }
    /*允许绑定地址快速重用 */
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int));
    /*结构体成员清零 */
    bzero(&sin, sizeof(sin));
    /*填充结构体 */
    sin.sin_family = AF_INET;
    sin.sin_port = htons(SERV_PORT);/*网络字节序端口号 */
    /*让服务器能收到任意客户端数据 */
    sin.sin_addr.s_addr = htonl(INADDR_ANY);
    /*绑定socket */
    if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
        perror("bind");
        exit(-1);
    }
    /*主动套接字转被动套接字 */
    if(listen(fd, BACKLOG) < 0)
    {
        perror("listen");
        exit(-1);
    }
    puts("Server staring ok");
    while(1)
    {
        /*阻塞等待客户端连接请求并保存客户端信息 */
        if((newfd = accept(fd, (struct sockaddr *)&client_message, &client_message_len)) < 0)
        {
            perror("accept");
            exit(-1);
        }
        /*客户端IP地址网络字节序转本地字节序 */
        if(!inet_ntop(AF_INET, (void *)&client_message.sin_addr.s_addr, clientIPV4, sizeof(client_message)))
        {
            perror("inet_ntop");
            exit(-1);
        }
        printf("client(%s: %d) is connected!\n", clientIPV4, ntohs(client_message.sin_port));
        pthread_create(&a_thread, NULL, client_thread, (void *)&newfd);
    }
    close(fd);
    return 0;
}
void *client_thread(void * arg)
{
    char buf[BUFSIZ];
    int ret = -1;/*read函数返回值 */
    int newfd = *(int *)arg;
    printf("thread newfd = %d\n", newfd);
    while(1)
    {
        /*与newfd进行读写 */
        bzero(buf, BUFSIZ);
        /*判断读函数是否出错 */
        do
        {
            ret = read(newfd, buf, BUFSIZ - 1);
        } while (ret < 0 && EINTR == errno);
        if(ret < 0)
        {
            perror("read");
            exit(-1);
        }
        if(0 == ret)
        {
            break;
        }
        printf("Service receive data: %s\n", buf);
        if(strcmp(buf, QUIT_STR) == 0)
        {
            printf("Client(fd = %d) is exting...\n", newfd);
            break;
        }
    }
    close(newfd);
}
  • 通用头文件
#ifndef __TCPC1_H__
#define __TCPC1_H__
/*网络相关头文件 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

#include <fcntl.h>

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>

#include <errno.h>

#include <pthread.h>

#include <signal.h>
#include <sys/wait.h>

#define SERV_PORT 5001
#define SERV "0.0.0.0"
#define SERV_IP_ADDR "47.105.70.108"
#define BACKLOG 5
#define QUIT_STR "quit\n"

#endif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容