网络编程_3(TCP)

该博客聚焦于TCP相关内容,介绍了TCP模型,包含listen、accept等操作。还阐述了服务器模型,循环服务器一次只能处理一个客户端,有不能处理耗时操作的缺点;并发服务器可同时处理多客户端请求,分为多进程和多线程并发服务器,并给出了代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

五、TCP

一二章请前往:网络编程_1(网络基础+跨主机传输
三四章请前往:网络编程_2(网络属性+UDP(UDP模型+广播组播))

(一)TCP模型

1.TCP模型

在这里插入图片描述

2.listen

功能:监听套接字;
头文件:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
原型:
       int listen(int sockfd, int backlog);
参数:
    int sockfd:用socket函数生成的流式套接字.要被转换成监听套接字的套接字。
    int backlog:允许建立多少链接。
    		内核会为每一个socket套接字维护两个队列.
    		未完成连接的队列。/proc/sys/net/ipv4/tcp_max_syn_backlogc
    		以完成连接的队列,等着accept函数拿走;
返回值:
    成功,返回0;
	失败,返回-1;

3.accept(阻塞函数)

功能:接收客户端的链接,并返回套接字;从已完成链接的队列头中,取出一个链接。(文件描述符);
头文件:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
原型:
       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
    int sockfd:已经被listen监听的套接字;
    struct sockaddr *addr:存储链接成功的客户端的信息:ip和端口。
           struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */
               in_port_t      sin_port;   /* port in network byte order */
               struct in_addr sin_addr;   /* internet address */
           };
           /* Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };
	socklen_t *addrlen:第二个结构体的大小,就是sizeof(struct sockaddr_in);
参数:
    成功,返回一个新的套接字,是与客户端建立链接的套接字;
		往这个套接字中读写消息,就是与客户端通讯;
	失败,返回-1,更新errno。

4.recv

功能:从套接字中获取数据;
头文件:
       #include <sys/types.h>
       #include <sys/socket.h>
原型:
       ssize_t recv(int sockfd, void *buf, size_t len, int flags);
       ssize_t read(int fd, void *buf, size_t count);
参数:
    int sockfd:从该文件描述符中读取数据; accept函数返回的新的套接字;
	void *buf:存储获取到的数据;
	size_t len:指定要接受的数据大小;
	int flags:接收方式:0,默认方式接收。阻塞;
返回值:
    > 0;接收的字符个数;
	=0; 对方关闭,对方调用close关闭文件描述符;
	=-1;函数调用出错.     
     errno = EINTR  The receive was interrupted by delivery of a signal before any data werec available;
	需要忽略犹豫信号干扰造成的数据传输中断。

5.send

功能:发送数据到套接字中;
头文件:
       #include <sys/types.h>
       #include <sys/socket.h>
原型:
       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
       ssize_t write(int fd, const void *buf, size_t count);
参数:
    int sockfd:套接字;
	void *buf:需要发送的数据;
	size_t len:需要发送的数据大小;
	int flags:发送方式:0,默认方式发送。阻塞;
返回值:
    成功,返回发送成功的数据个数;
	失败,返回-1,更新errno;
	errno = EINTR  The receive was interrupted by delivery of a signal before any data were available;
	需要忽略犹豫信号干扰造成的数据传输中断。

6.connect

功能:连接服务器;
头文件:
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
原型:
       int connect(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);
参数:
    int sockfd:套接字;
	struct sockaddr *addr:通用结构体,iPv4的结构体,填充服务器的地址信息和端口;
	socklen_t addrlen:第二个结构体的大小;
返回值:
    成功,返回0;
	失败,返回-1,跟新errno;

7.shutdown

#include <sys/socket.h>
int shutdown(int sockfd,int how)
参数:
    SHUT_RD关闭读端,无法从套接字种读取数据
    SHUT_WR关闭写端,无法使用套接字发送数据
    SHUT_RDWR关闭读写端,则无法读写
返回值:
    成功返回0
    失败返回-1,设置errno  

(二)服务器模型

1.循环服务器

1)一次只能处理一个客户端,等这个客户端退出后,才能处理下一个客户端。
2)缺点循环服务器所处理的客户端不能有耗时操作

socket();
bind();
listen();
while(1){
    newfd = accept();
    while(1){
        recv();
        fun`````
    }
    close();
}
close();

例子

服务器
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
    //1.创建套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd < 0)
    {
        perror("socket");
        return -1;
    }
    //允许本地端口快速重用
    int reuse = 1;
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
    //2.绑定
    //填充服务器信息
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(2020);
    sin.sin_addr.s_addr = inet_addr("0");
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin))<0)
    {
        perror("bind");
        exit(1);
    }
    printf("绑定成功\n");
    //3.监听
    if(listen(sfd, 3)<0)
    {
        perror("listen");
        exit(1);
    }
    //4.接收客户端的连接
    struct sockaddr_in cin;
    socklen_t len = sizeof(cin);
    int cntfd = -1;
    while(1)
    {
        cntfd = accept(sfd, (void*)&cin, &len);
        if(cntfd < 0)
        {
            perror("accept");
            exit(1);
        }
        printf("客户端链接成功 %d\n", cntfd);
        char ip[20] = "";
        inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
        printf("[%s:%d]连接成功\n", ip, ntohs(cin.sin_port));
        int ret = -1;
        while(1)
        {
            //5.recv接收数据
            char buf[256] = "";
            bzero(buf, sizeof(buf));
            do
            {
                ret= recv(cntfd, buf, 256, 0);
            }while(ret<0 && errno == EINTR);
            if(ret < 0)
            {
                perror("recv");
                exit(1);
            }
            else if(0 == ret)
            {
                fprintf(stderr, "对方关闭\n");
                break;
            }
            printf("%s\n", buf);
        }
        close(cntfd);
    }
    close(sfd);c
    return 0;
}
客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main(int argc, const char *argv[])
{
    //1.创建套接字
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0)
    {
        perror("socket");
        return -1;
    }
    //允许本地端口快速重用
    int reuse = 1;
    int len = sizeof(reuse);
    if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, len)<0)
    {
        perror("setsockopt");
        exit(1);
    }
    //2.绑定 非必须
    //3.连接服务器
    //填充服务器信息
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(2020);
    sin.sin_addr.s_addr = inet_addr("192.168.1.141");
    //int connect(int sockfd, \
    //const struct sockaddr *addr,  socklen_t addrlen);
    if(connect(fd, (void*)&sin, sizeof(sin))<0)
    {
        perror("connect");
        exit(1);
    }
    printf("连接成功\n");
    char buf[256] = "";
    //4.发送消息
    int ret = -1;
    while(1)
    {
        bzero(buf, sizeof(buf));
        fprintf(stderr, "请输入:");
        fgets(buf, 256-1, stdin);
        // ssize_t send(int sockfd, const void *buf, size_t len, int flags);
        do
        {
            ret = send(fd, buf, strlen(buf), 0);
        }while(ret<0 && errno == EINTR);
        if(ret < 0)
        {
            perror("send");
            exit(1);
        }
    }
    close(fd);                                                                         
    return 0;
}

2.并发服务器

1)可以同时处理多个客户端请求,创建子进程或者线程来处理客户端的请求
2)父进程/主线程只负责连接,子进程/分支线程 只负责与客户端进行交互。

1.多进程并发服务器
void handler(int signo) {
    waitpid()
}
sockfd = socket();
bind();
listen();
signal(SIGCHLD,handler);
while(1){
    confd=accpet(); 
    /*******子进程*******/
    if(fork()==0){
        close(sockfd);
        while(1){
            recv();
            fun------;c
        }
        exit(1);
    }
    /*******父进程*******/
    close(confd);
}
close(scokfd);
代码实现
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
void show_info(struct sockaddr_in cin);
void recv_cli_msg(int newfd, struct sockaddr_in cin);
void handler(int sig)
{
    //信号处理函数中不能有耗时操作
    if (signo == SIGCHLD) {
    while(waitpid(-1,NULL,WNOHANG) > 0);
  //循环回收多个子进程,
  //当waitpid非阻塞成功回收子进程后,
  //继续尝试回收一次,如果回收成功,继续尝试回收
  //如果回收失败,说明没有僵尸进程需要回收了
}
int main(int argc, const char *argv[])
{
    //捕获信号
    if(signal(SIGCHLD, handler) == SIG_ERR)
    {
        perror("signal");
        return -1;
    }
    //1.创建套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd < 0)
    {
        perror("socket");
        return -1;
    }
    //允许本地端口快速重用
    int reuse = 1;
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
    //2.绑定
    //填充服务器信息
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(2020);
    sin.sin_addr.s_addr = inet_addr("0");
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin))<0)
    {
        perror("bind");
        exit(1);
    }
    printf("绑定成功\n");
    //3.监听
    if(listen(sfd, 3)<0)
    {
        perror("listen");
        exit(1);
    }
    //4.接收客户端的连接
    struct sockaddr_in cin;
    socklen_t len = sizeof(cin);
    int cntfd = -1;
    while(1)
    {
        cntfd = accept(sfd, (void*)&cin, &len);
        if(cntfd < 0)
        {
            perror("accept");
            exit(1);
        }
        printf("客户端链接成功 %d\n", cntfd);
        show_info(cin);
        int ret = -1;
        pid_t pid = fork();
        if(0 == pid)
        {          
            //子进程 与客户端进行数据交互;
            close(sfd);
            recv_cli_msg(cntfd, cin);
            close(cntfd);
            exit(1);
        }
        else if(pid > 0)
        {
            //父进程直接返回。
            close(cntfd);
        }
        else
        {
            perror("fork");
            close(cntfd);
        }
    }
    close(sfd);
    return 0;
}
void show_info(struct sockaddr_in cin)
{
    char ip[20] = "";
    inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
    printf("[%s:%d]连接成功\n", ip, ntohs(cin.sin_port));
}
void recv_cli_msg(int newfd, struct sockaddr_in cin)
{
    char buf[256] = "";
    int ret = -1;
    char ip[20] = "";
    inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
    int port = ntohs(cin.sin_port);
    while(1)
    {
        bzero(buf, sizeof(buf));
        //接收数据
        do
        {
            ret = recv(newfd, buf, 256, 0);
        }while(ret < 0 && EINTR == errno);
        if(ret < 0)
        {
            perror("recv");
            return;
        }
        else if(0 == ret)
        {
            fprintf(stderr, "[%s:%d] 客户端断开连接\n",ip, port);
            return;
        }
        printf("[%s:%d]:%s\n", ip, port, buf);
    }
}
2.多线程并发服务器
sockfd = socket();
bind();
listen();
while(1){
    confd=accpet(); 
    /****子线程********/c
	pthread_create() --> func接收消息
    /****子线程********/
    pthrad_detach()
}
close(scokfd);
代码实现
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>
typedef struct
{
    int newfd;
    struct sockaddr_in cin;
}__cli_info;
void show_info(struct sockaddr_in cin);
void* recv_cli_msg(void* arg);
int main(int argc, const char *argv[])
{
    //1.创建套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd < 0)
    {
        perror("socket");
        return -1;
    }
    //允许本地端口快速重用
    int reuse = 1;
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
    //2.绑定
    //填充服务器信息
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;
    sin.sin_port = htons(2020);
    sin.sin_addr.s_addr = inet_addr("0");
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin))<0)
    {
        perror("bind");
        exit(1);
    }
    printf("绑定成功\n");
    //3.监听
    if(listen(sfd, 3)<0)
    {
        perror("listen");
        exit(1);
    }
    printf("服务器启动成功\n");
    //4.接收客户端的连接
    struct sockaddr_in cin;
    socklen_t len = sizeof(cin);
    int cntfd = -1;
    while(1)
    {
        cntfd = accept(sfd, (void*)&cin, &len);
        if(cntfd < 0)
        {
            perror("accept");
            exit(1);
        }
        printf("客户端链接成功 %d\n", cntfd);
        show_info(cin);
        int ret = -1;
        //创建一个线程
        __cli_info cliInfo;
        cliInfo.newfd = cntfd;
        cliInfo.cin = cin;
        //有一个客户端一旦连接成功,就创建一个线程,作为与客户到交互的线程。
        pthread_t tid = -1;
        if(pthread_create(&tid, NULL, recv_cli_msg, (void*)&cliInfo) < 0)
        {
            perror("pthread_create");
            close(cntfd);
        }
    }
    close(sfd);
    return 0;
}
void show_info(struct sockaddr_in cin)
{
    char ip[20] = "";
    inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
    printf("[%s:%d]连接成功\n", ip, ntohs(cin.sin_port));
}
void* recv_cli_msg(void* arg)
{
    //分离线程,线程退出后自动回收资源;
    pthread_detach(pthread_self());
    __cli_info cli = *(__cli_info*)arg;
    int newfd = cli.newfd;
    struct sockaddr_in cin = cli.cin;
    char ip[20] = "";
    inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
    int port = ntohs(cin.sin_port);
    char buf[256] = "";
    int ret = -1;
    while(1)
    {
        bzero(buf, sizeof(buf));
        //接收数据
        do
        {
            ret = recv(newfd, buf, 256, 0);
        }while(ret < 0 && EINTR == errno);
        if(ret < 0)
        {
            perror("recv");
            break;
        }
        else if(0 == ret)
        {
            fprintf(stderr, "[%s:%d]断开连接\n", ip, port);
            break;
        }
        printf("[%s:%d]:%s\n", ip,port, buf);
    }
    close(newfd);
    pthread_exit(NULL);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值