LinuxC网络编程

文章目录

  • 一、socket
  • 二、bind
  • 三、setsockopt
  • 四、listen(TCP服务端)
  • 五、accept(TCP服务端)
  • 六、connect(TCP客户端)
  • 七、send&recv(TCP)
  • 八、sendto&recvfrom(UDP)
  • 九、shutdown
  • 十、epoll_create
  • 十一、epoll_ctl
  • 十二、epoll_wait
  • 十三、循环服务器模型(TCP)
  • 十四、循环服务器模型(UDP)
  • 十五、并发服务器模型(TCP多线程)
  • 十六、并发服务器模型(TCP多进程)

在这里插入图片描述

一、socket

#include 
#include 

int socket(int domain, int type, int protocol);

功能:创建一个套接字文件,然后以文件形式来操作通信,不过套接字文件没有文件名。

参数

  • domain:用于指定协议族是IPV4还是IPV6,分别对应参数AF_INET和参数AF_INET6
  • type:套接字类型,用于进一步指定使用协议族中的哪个子协议来通信:
    ①如果指定为SOCK_STREAM,表示使用TCP协议
    ②如果指定为SOCK_DGRAM,表示使用UDP协议
    ③如果指定为SOCK_RDM,表示使用原始网络通信,即IP协议
    ④如果指定为SOCK_NONBLOCK,表示将socket返回的文件描述符指定为非阻塞的,它可以与前面的宏进行或运算
  • protocol:表示传输协议,一般情况下可以直接写0,有操作系统自动推演出应该使用什么协议。

返回值:成功返回套接字描述符,失败返回-1并设置errno。


二、bind

#include 
#include 

#include 

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

struct sockaddr_in
{
    short int          sin_family; /* Internet地址族 */
    unsigned short int sin_port; /* 端口号 */
    struct in_addr     sin_addr; /* IP地址 */
    unsigned char      sin_zero[8]; /* 填0 */
};

struct in_addr
{
    unsigned long s_addr; /* IPV4的32位IP地址 */
};

功能:将指定了通信协议的套接字文件与IP以及端口绑定起来。

参数

  • sockfd:套接字的文件描述符。
  • addr:指定要绑定的参数信息。编程中一般并不直接针对sockaddr数据结构操作,而是使用与sockaddr等价的sockaddr_in数据结构。
  • addrlen:第二个参数的结构的长度。

此外,由于网络字节序有一般采用大端(高尾端)排序方式,所以从主机向网络发送从网络向主机接收时要进行字节序的转换

#include 

uint32_t htonl(uint32_t hostlong); /* host to network short */
uint16_t htons(uint16_t hostshort); /* host to network short */
uint32_t ntohl(uint32_t netlong); /* network to host long */
uint16_t ntohs(uint16_t netshort); /* network to host short */

同时,IP地址的格式也要在字符串和32位整数之间相互转化

#include 
#include 
#include 

int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in);
struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);

三、setsockopt

#include 
#include 

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

如果是服务器主动关闭连接,默认需要等待2MSL才能将端口资源释放,这会导致此端口短时间内无法重用,于是我们通过下面这段函数将该socket设置为可重用:

/* 设置套接字为可重用 */
int option = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

LinuxC网络编程_第1张图片

LinuxC网络编程_第2张图片


四、listen(TCP服务端)

#include 
#include 

int listen(int sockfd, int backlog);

功能:将套接字文件描述符,从主动文件描述符变为被动文件描述符,然后用于被动监听客户的连接。

参数

  • sockfd:socket返回的套接字文件描述符。
  • backlog:指定的队列容量,这个队列用于记录正在连接,但是还没连接完成的客户,一般将队列容量指定为2、3即可。这个容量并没有什么统一的设定值,一般来说只要小于30即可。

五、accept(TCP服务端)

#include 
#include 

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

#define _GNU_SOURCE
#include 

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

功能:被动监听客户发起三次握手的连接请求,三次握手成功,即建立连接成功。accept被动监听客户连接的过程其实也是监听客户上线的过程。对于那些只连接了一般,还未连接完成的客户,会被记录到未完成队列中,队列的容量由listen函数的第二个参数backlog来指定。服务端调用accept函数监听连接,客户端调用connect来请求连接。一旦连接成功,服务器这边的TCP协议会记录客户的IP和端口,如果是跨网通信的,记录IP的就是客户所在的路由器的公网IP。

参数

  • sockfd:一个已经通过listen函数转化的被动套接字文件描述符。
  • addr:用于记录发起连接请求的那个客户端的IP和端口。
  • addrlen:用一个变量记录第二个参数的长度,addrlen对应这个变量的地址。

返回值:如果执行成功返回新的套接字文件描述符,否则返回-1并设置errno。


六、connect(TCP客户端)

#include 
#include 

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

功能:向服务器主动发起连接请求,即主动发起三次握手。

参数

  • sockfd:socket函数所返回的套接字文件描述符。
  • addr:用于设置你所要连接的服务器的IP和端口。如果只是纯粹的局域网内部通信的话,IP就是局域网IP,但如果是跨网通信的话,IP必须是服务器所在的路由器的公网IP。为了方便操作,在应用层我们还是使用struct sockaddr_in来设置,然后传递给connect时在强制转换为struct sockaddr
  • addrlen:第二个参数所指定的结构体变量的大小。

七、send&recv(TCP)

#include 
#include 

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

功能:向对方发送数据或从对方接收数据。

参数

  • sockfd:用于通信的通信描述符。通信描述符在客户端对应socket函数生成的套接字描述符,在服务端则是accept返回的套接字描述符。
  • buf:应用缓存,存放要发送的或待接收的数据,一般情况下为一个结构体。
  • flags:一般置为0,表示阻塞发送或阻塞接收,如果想要非阻塞读取或发送则置为MSG_DONTWAIT

八、sendto&recvfrom(UDP)

#include 
#include 

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

功能:发送或接收数据,最后两个参数均为NULL时功能与send和recv相同。

参数

  • sockfd:socket返回的套接字。对于UDP来说,套接字描述符直接用于通信。
  • buf:存放数据的应用缓存。
  • len:应用缓存的大小。
  • flags:一般写0,表示阻塞发送数据。
  • dest_addr&src_addr:IP地址以及端口等套接字信息。由于UDP不面向连接,无法自动记录对方的套接字信息,所以每次数据的发送都需要指定套接字信息。
  • addrlen:套接字信息结构体的大小。

返回值:成功返回发送或接收到的字节数,失败返回-1并设置errno。


九、shutdown

#include 

int shutdown(int sockfd, int how);

功能:可以按照要求关闭连接,而且不管有多少个描述符指向同一个连接,shutdown可以一次性将其全部关闭。

参数

  • sockfd:服务端使用accept函数返回的描述符。
  • how:如何断开连接。SHUT_RD只断开读连接,SHUT_WR只断开写连接,SHUT_RDWR将读、写连接全部断开。

shutdownclose的区别:

  1. close只能将读写同时关闭,而shutdown可以选择性关闭。
  2. close只能关闭单个描述符,二shutdown在关闭时会将如选定连接有关的全部描述符关闭。

十、epoll_create

#include 

int epoll_create(int size);
int epoll_create1(int flags);

功能epoll_create()函数用于创建一个epoll实例,它实际上是由一个红黑树来实现的。

参数

  • size:用于告诉内核这个epoll需要关注的描述符的大致数目而不是最大个数。实际上Linux在2.6.8版本以后会忽略这个参数,但这个参数必须大于零。当容量不足时epoll会自动扩容。

返回值:返回创建的epoll红黑树的文件描述符。


十一、epoll_ctl

#include 

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

typedef union epoll_data {
	void        *ptr;
	int          fd;
	uint32_t     u32;
	uint64_t     u64;
} epoll_data_t;

struct epoll_event {
	uint32_t     events; /* Epoll events */
	epoll_data_t data;   /* User data variable */
};

功能:用于操作创建的epoll实例,主要包括增加、修改和删除三种操作。

参数

  • epfd:epoll的文件描述符。
  • op:对epoll执行的操作,主要包括:
    EPOLL_CTL_ADD,向epoll中注册新的文件描述符。
    EPOLL_CTL_MOD,修改已注册文件描述符的监听事件。
    EPOLL_CTL_DEL,从epoll中删除文件描述符。
  • fd:操作关联的文件描述符。
  • event:告诉内核要监听什么事件,实际上epoll的红黑树每个结点上都对应一个epoll_event类型的结构体。结构体中的成员events主要包括以下宏:
    EPOLLIN:表示对应的文件描述符可以读。
    EPOLLOUT:表示对应的文件描述符可以写。
    EPOLLPRI:表示对应的文件描述符有紧急数据或者带外数据到来。
    EPOLLERR:表示对应的文件描述符发生错误。
    EPOLLHUP:表示对应的文件描述符被挂断。
    EPOLLET:将epoll设置为边缘触发模式。
    EPOLLONESHOT:只监听一次事件,当监听完这次事件后,如果还需要继续监听这个socket的话,需要将它再次加入到epoll队列里。

返回值:执行成功返回0,否则返回-1并设置errno的值。


十二、epoll_wait

#include 

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask);

功能

参数

  • epfd:epoll的文件描述符。
  • events:一个结构体指针,当监听的文件描述符发生改变时,内核会把发生了改变的epoll_event拷贝到这个参数里。
  • maxevents:数组的容量。
  • timeout:超时信息:
    ①为-1时表示永久阻塞。
    ②为0时表示立即返回。
    ③取>0时表示监听到目标数目的文件描述符时再返回。

返回值:执行成功返回发出请求的文件描述符的个数,执行失败返回-1并设置error。


十三、循环服务器模型(TCP)

服务端 server.c

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#define PORT 2015
#define IP "192.168.179.129"
#define MAX_SIZE 1024

int main() {
    int sockfd;//服务器套接字描述符
    int client_sockfd;//通信套接字描述符
    char buffer[MAX_SIZE];//数据缓冲区
    struct sockaddr_in server_addr;//服务器套接字信息
    struct sockaddr_in client_addr;//客户端套接字信息

    /* 获取服务器套接字文件描述符 */
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket error");
        exit(1);
    }

    /* 初始化服务器套接字信息 */
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;//使用IPv4
    server_addr.sin_port = htons(PORT);//对端口进行字节序转化
    server_addr.sin_addr.s_addr = inet_addr(IP);//对IP地址进行格式转化

    /* 因为最后需要服务器主动关闭连接,所以要设置服务器套接字为可重用 */
    int option = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

    /* 绑定服务器套接字信息 */
    if (bind(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr_in)) < 0) {
        perror("bind error");
        exit(1);
    }

    /* 将服务器套接字转化为被动监听 */
    if (listen(sockfd, 3) < 0) {
        perror("listen error!");
        exit(1);
    }

    /* 与客户端进行串行连接 */
    while(1) {
        printf("[server] Server is waiting······\n");

        /* 等待客户端的连接 */
        int len = sizeof(struct sockaddr_in);//通信套接字结构体长度
        memset(&client_addr, 0, sizeof(struct sockaddr_in));
        if ((client_sockfd = accept(sockfd, (struct sockaddr *)(&client_addr), &len)) < 0) {
            perror("accept error");
            exit(1);
        }
        printf("[server] Client's port is %d, ip is %s\n", ntohs(client_addr.sin_port), inet_ntoa(client_addr.sin_addr));

        /* 接收客户端的消息 */
        memset(buffer, 0, MAX_SIZE);
        recv(client_sockfd, buffer, sizeof(buffer), 0);
        printf("[server] Client's message:%s\n", buffer);
        
        /* 向客户端发送消息 */
        send(client_sockfd, "I have received your message.", 30, 0);

        /* 关闭连接 */
        shutdown(client_sockfd, SHUT_RDWR);
    }

    return 0;    
}

客户端 client.c

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#define PORT 2015
#define IP "192.168.179.129"
#define MAX_SIZE 1024

int main() {
    int sockfd;//客户端套接字描述符
    char buffer[MAX_SIZE];//收发数据缓冲区
    struct sockaddr_in server_addr;//服务器套接字描述符

    /* 获取客户端套接字文件描述符 */
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket error");
        exit(1);
    }

    /* 初始化服务器套接字信息 */
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = inet_addr(IP);

    /* 向服务器发送连接请求 */
    if (connect(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr_in)) < 0) {
        perror("connect error");
        exit(1);
    }
    printf("[client] Connect successfully.\n");

    /* 向服务器发送消息 */
    printf("[client] Please input your message>>>");
    memset(buffer, 0, MAX_SIZE);
    scanf("%[^\n]%*c", buffer);
    send(sockfd, buffer, strlen(buffer), 0);

    /* 接收服务端的消息 */
    memset(buffer, 0, MAX_SIZE);
    recv(sockfd, buffer, sizeof(buffer), 0);
    printf("[client] Server's message:%s\n", buffer);

    return 0;
}

运行结果

LinuxC网络编程_第3张图片


十四、循环服务器模型(UDP)

服务端 server.c

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#define CLIENT_PORT 2015
#define SERVER_PORT 2025
#define IP "192.168.179.129"
#define MAX_SIZE 1024

int main() {
    int sockfd;//服务端套接字描述符
    char buffer[MAX_SIZE];//收发数据缓冲区
    struct sockaddr_in client_addr;//客户端套接字信息
    struct sockaddr_in server_addr;//服务端套接字信息

    /* 获取服务端套接字描述符 */
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket error");
        exit(1);
    }

    /* 初始化服务端套接字信息 */
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = (SERVER_PORT);
    server_addr.sin_addr.s_addr = inet_addr(IP);

    /* 绑定接收端信息 */
    if (bind(sockfd, (struct sockaddr *) &server_addr, sizeof(struct sockaddr_in)) < 0) {
        perror("bind error");
        exit(1);
    }

    /* 接收消息 */
    while (1) {
        /* 初始化客户端套接字信息 */
        memset(&client_addr, 0, sizeof(struct sockaddr_in));
        client_addr.sin_family = AF_INET;
        client_addr.sin_port = (CLIENT_PORT);
        client_addr.sin_addr.s_addr = inet_addr(IP);

        /* 收发消息 */
        printf("[server] Srever is waiting······\n");
        int len = sizeof(struct sockaddr_in);
        memset(buffer, 0, sizeof(buffer));
        recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *) &client_addr, &len);
        printf("[server] Client's port is %d, ip is %s.\n", ntohs(client_addr.sin_port), inet_ntoa(client_addr.sin_addr));
        printf("[server] Client's message:%s\n", buffer);
        memset(buffer, 0, sizeof(buffer));
        sendto(sockfd, "I have received your message.", 30, 0, (struct sockaddr *) &client_addr, sizeof(struct sockaddr_in));
    }

    return 0;
}

客户端 client.c

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#define CLIENT_PORT 2015
#define SERVER_PORT 2025
#define IP "192.168.179.129"
#define MAX_SIZE 1024

int main() {
    int sockfd;//客户端套接字描述符
    char buffer[MAX_SIZE];//收发数据缓冲区
    struct sockaddr_in client_addr;//客户端套接字信息
    struct sockaddr_in server_addr;//服务端套接字信息

    /* 获取客户端套接字描述符 */
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket error");
        exit(1);
    }

    /* 初始化服务端套接字信息 */
    memset(&client_addr, 0, sizeof(struct sockaddr_in));
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = (CLIENT_PORT);
    client_addr.sin_addr.s_addr = inet_addr(IP);

    /* 绑定接收端信息 */
    if (bind(sockfd, (struct sockaddr *) &client_addr, sizeof(struct sockaddr_in)) < 0) {
        perror("bind error");
        exit(1);
    }

    /* 接收消息 */
    while (1) {
        /* 初始化服务端套接字信息 */
        memset(&server_addr, 0, sizeof(struct sockaddr_in));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = (SERVER_PORT);
        server_addr.sin_addr.s_addr = inet_addr(IP);

        /* 收发消息 */
        printf("[client] Please input your message>>>");
        memset(buffer, 0, sizeof(buffer));
        scanf("%[^\n]%*c", buffer);
        sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *) &server_addr, sizeof(struct sockaddr_in));
        int len = sizeof(struct sockaddr_in);
        recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *) &server_addr, &len);
        printf("[client] Server's port is %d, ip is %s.\n", ntohs(server_addr.sin_port), inet_ntoa(server_addr.sin_addr));
        printf("[client] Server's message:%s\n", buffer);
        memset(buffer, 0, sizeof(buffer));
    }

    return 0;
}

运行结果

LinuxC网络编程_第4张图片


十五、并发服务器模型(TCP多线程)

服务端 server.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#define PORT 2015
#define IP "192.168.179.129"
#define MAX_SIZE 1024

void *recv_thread_fun(void *arg);

int main() {
    int server_sockfd;//服务器套接字描述符
    int client_sockfd;//通信套接字描述符
    pthread_t recv_thread_id;
    struct sockaddr_in server_addr;//服务器套接字信息
    struct sockaddr_in client_addr;//客户端套接字信息

    /* 获取服务器套接字文件描述符 */
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sockfd == -1) {
        perror("socket error");
        exit(1);
    }

    /* 初始化服务器套接字信息 */
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;//使用IPv4
    server_addr.sin_port = htons(PORT);//对端口进行字节序转化
    server_addr.sin_addr.s_addr = inet_addr(IP);//对IP地址进行格式转化

    /* 因为最后需要服务器主动关闭连接,所以要设置服务器套接字为可重用 */
    int option = 1;
    setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

    /* 绑定服务器套接字信息 */
    if (bind(server_sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr_in)) < 0) {
        perror("bind error");
        exit(1);
    }

    /* 将服务器套接字转化为被动监听 */
    if (listen(server_sockfd, 3) < 0) {
        perror("listen error!");
        exit(1);
    }

    /* 与客户端进行串行连接 */
    while(1) {
        printf("[server] Server is waiting······\n");

        /* 等待客户端的连接 */
        int len = sizeof(struct sockaddr_in);//通信套接字结构体长度
        memset(&client_addr, 0, sizeof(struct sockaddr_in));
        if ((client_sockfd = accept(server_sockfd, (struct sockaddr *)(&client_addr), &len)) < 0) {
            perror("accept error");
            exit(1);
        }
        printf("[server] Client %d port is %d, ip is %s.\n", client_sockfd,ntohs(client_addr.sin_port), inet_ntoa(client_addr.sin_addr));

        /* 创建子线程用于收发消息 */
        if (pthread_create(&recv_thread_id, NULL, recv_thread_fun, (void *) &client_sockfd) != 0) {
            perror("pthread create error");
            exit(1);
        }
    }

    return 0;    
}

/* 子线程运行函数 */
void *recv_thread_fun(void *arg) {
    int client_sockfd = *((int *) arg);
    char buffer[MAX_SIZE];//数据缓冲区

    pthread_detach(pthread_self());//将本线程转换为分离态,由系统自动回收资源

    while (1) {
        /* 接收客户端的消息 */
        memset(buffer, 0, MAX_SIZE);
        if (recv(client_sockfd, buffer, sizeof(buffer), 0) == 0) {
            /* 监测客户端的退出 */
            printf("[server] Client %d has exited.\n", client_sockfd);
            pthread_exit(NULL);
        }
        printf("[server] Client %d message:%s\n", client_sockfd, buffer);

        /* 向客户端发送消息 */
        send(client_sockfd, "I have received your message.", 30, 0);
    }
}

客户端 client.c

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#define PORT 2015
#define IP "192.168.179.129"
#define MAX_SIZE 1024

int main() {
    int client_sockfd;//客户端套接字描述符
    char buffer[MAX_SIZE];//收发数据缓冲区
    struct sockaddr_in server_addr;//服务器套接字描述符

    /* 获取客户端套接字文件描述符 */
    if((client_sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket error");
        exit(1);
    }

    /* 初始化服务器套接字信息 */
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = inet_addr(IP);

    /* 向服务器发送连接请求 */
    if (connect(client_sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr_in)) < 0) {
        perror("connect error");
        exit(1);
    }
    printf("[client] Connect successfully.\n");

    while (1) {
        /* 向服务器发送消息 */
        printf("[client] Please input your message>>>");
        memset(buffer, 0, MAX_SIZE);
        scanf("%[^\n]%*c", buffer);
        if (!buffer[0]) break;//输入空代表结束通信
        send(client_sockfd, buffer, strlen(buffer), 0);

        /* 接收服务端的消息 */
        memset(buffer, 0, MAX_SIZE);
        recv(client_sockfd, buffer, sizeof(buffer), 0);
        printf("[client] Server's message:%s\n", buffer);
    }
    
    /* 关闭连接 */
    shutdown(client_sockfd, SHUT_RDWR);

    return 0;
}

运行结果

LinuxC网络编程_第5张图片


十六、并发服务器模型(TCP多进程)

服务端 server.c

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#define SERVER_PORT 1521
#define IP "0.0.0.0"
#define MAX_SIZE 1024

int main() {
    int server_sockfd;//服务器套接字描述符
    int client_sockfd;//通信套接字描述符
    struct sockaddr_in server_addr;//服务器套接字信息
    struct sockaddr_in client_addr;//客户端套接字信息

    /* 获取服务器套接字文件描述符 */
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sockfd == -1) {
        perror("socket error");
        exit(1);
    }

    /* 初始化服务器套接字信息 */
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;//使用IPv4
    server_addr.sin_port = htons(SERVER_PORT);//对端口进行字节序转化
    server_addr.sin_addr.s_addr = inet_addr(IP);//对IP地址进行格式转化

    /* 因为最后需要服务器主动关闭连接,所以要设置服务器套接字为可重用 */
    int option = 1;
    setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

    /* 绑定服务器套接字信息 */
    if (bind(server_sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr_in)) < 0) {
        perror("bind error");
        exit(1);
    }

    /* 将服务器套接字转化为被动监听 */
    if (listen(server_sockfd, 3) < 0) {
        perror("listen error!");
        exit(1);
    }

    /* 与客户端进行串行连接 */
    while(1) {
        printf("[server] Server is waiting······\n");

        /* 等待客户端的连接 */
        int len = sizeof(struct sockaddr_in);//通信套接字结构体长度
        memset(&client_addr, 0, sizeof(struct sockaddr_in));
        if ((client_sockfd = accept(server_sockfd, (struct sockaddr *)(&client_addr), &len)) < 0) {
            perror("accept error");
            exit(1);
        }
        printf("[server] Client %d port is %d, ip is %s.\n", client_sockfd,ntohs(client_addr.sin_port), inet_ntoa(client_addr.sin_addr));

        int pid = fork();//创建子进程用于收发消息
        if (pid > 0) {
            close(client_sockfd);
        } else if (pid == 0) {
            char buffer[1024];
            
            while (1) {
                /* 接收客户端的消息 */
                memset(buffer, 0, MAX_SIZE);
                if (recv(client_sockfd, buffer, sizeof(buffer), 0) == 0) {
                    /* 监测客户端的退出 */
                    printf("[server] Client %d has exited.\n", client_sockfd);
                    exit(0);
                }
                printf("[server] Client %d message:%s\n", client_sockfd, buffer);

                /* 向客户端发送消息 */
                send(client_sockfd, "I have received your message.", 30, 0);
            }
        } else {
            perror("fork error");
            exit(1);
        }
    }

    return 0;    
}

客户端 client.c

#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 

#define PORT 1521
#define IP "120.78.188.0"
#define MAX_SIZE 1024

int main() {
    int client_sockfd;//客户端套接字描述符
    char buffer[MAX_SIZE];//收发数据缓冲区
    struct sockaddr_in server_addr;//服务器套接字描述符

    /* 获取客户端套接字文件描述符 */
    if((client_sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket error");
        exit(1);
    }

    /* 初始化服务器套接字信息 */
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = inet_addr(IP);

    /* 向服务器发送连接请求 */
    if (connect(client_sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr_in)) < 0) {
        perror("connect error");
        exit(1);
    }
    printf("[client] Connect successfully.\n");

    while (1) {
        /* 向服务器发送消息 */
        printf("[client] Please input your message>>>");
        memset(buffer, 0, MAX_SIZE);
        scanf("%[^\n]%*c", buffer);
        if (!buffer[0]) break;//输入空代表结束通信
        send(client_sockfd, buffer, strlen(buffer), 0);

        /* 接收服务端的消息 */
        memset(buffer, 0, MAX_SIZE);
        recv(client_sockfd, buffer, sizeof(buffer), 0);
        printf("[client] Server's message:%s\n", buffer);
    }
    
    /* 关闭连接 */
    shutdown(client_sockfd, SHUT_RDWR);

    return 0;
}

运行结果

LinuxC网络编程_第6张图片

LinuxC网络编程_第7张图片

你可能感兴趣的