TCP_server

本文介绍了网络编程的基本概念,包括创建套接字、绑定IP与端口、监听与接受连接等核心步骤,并提供了单进程、多进程及多线程版本的服务器端与客户端代码实现。

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

编写代码前首先了解几个重要接口:

创建套接字:

创建套接字,其实是在创建一个文件描述符。

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

参数:
domain :AF_INET 使用IPV4协议;
type:SOCK_STREAM 使用TCP协议;
protocol:0 状态参数;
返回值: 成功返回一个文件描述符sockfd,失败返回-1;

绑定IP,port:

#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

参数:
sockfd:sock 要被绑定的文件描述符;
addr:sockaddr数据结构的地址;
addrlen:sockadde数据结构的大小(字节);
返回值:成功返回0,失败返回-1;

参数中的‘struct sockaddr’ 结构体,是sock地址的泛型化接口,我们使用IPV4协议,使用的sockaddr接口应该是‘struct sockaddr_in’,因此在使用的时候需要进行地址的强制转化。

使用命令: grep -ER ‘struct sockaddr_in {’ /usr/include/
显示:/usr/include/linux/in.h:struct sockaddr_in {
键入命令: vim /usr/include/linux/in.h

可以查找结构体定义,如图:
这里写图片描述
因此在绑定IP,port之前首先要对’struct sockaddr_in’的对象进行赋值,也就是后面代码中的:

struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = inet_addr(ip);
local.sin_port = htons(port);

监听函数:

#include<sys/types.h>
#include<sys/socket.h>
int listen(int sockfd,int backlog);

参数:
sockfd:sock 要监听的文件描述符;
backlog:10 sockfd等待连接队列可能增长的最大长度;
返回值:成功返回0,失败返回-1;

接受一个连接:

#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);

参数:
sockfd:listen_sock 监听到连接的文件描述符
addr:&client 记录客户端IP,port的一个sockaddr结构的指针
addrlen:&len 调用者对其进行初始化,即client方的sockaddr结构大小
返回值:成功返回一个服务型的文件描述符,失败返回-1
因为要获取client方的IP,port信息,因此在函数调用前要先构建一个结构体对象:

struct sockaddr_in client;
socklen_t len = sizeof(client);

连接一台服务器:

#include<sys.types.h>
#include<sys/socket.h>
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);

参数:
sockfd:若该套接字是SOCK_STREAM,则尝试进行连接
addr:客户端sockaddr的地址
addrlen:客户端sockaddr的地址长度
返回值:成功返回0,失败返回-1

下面看看单进程版本服务器的代码,以及客户端代码:

//服务器server端:

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


void usage(char* str)
{
    printf("usage:%s [local_addr] [local_port]\n",str);
}

int startup(char* ip,int port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(2);
    }

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port =htons( port);
    local.sin_addr.s_addr = inet_addr(ip);

    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        perror("bind");
        exit(3);
    }

    if(listen(sock,10)<0)
    {
        perror("listen");
        exit(4);
    }
    return sock;
}

int main(int argc,char* argv[])
{
    if(argc<3)
    {
        usage(argv[0]);
        exit(1);
    }

    int listen_sock = startup(argv[1],atoi(argv[2]));

    while(1)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);

        if(new_sock<0)
        {       
            perror("accept");
            continue;
        }
        printf("get a client: [%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));

        char buf[1024];
        while(1)
        {
            ssize_t s = read(new_sock,buf,sizeof(buf)-1);
            if(s>0)
            {
                buf[s] = 0;
                if(buf[0] != 0)
                {
                    printf("client:# %s\n",buf);
                    printf("Please Enter:");
                    fgets(buf,sizeof(buf),stdin);
                    write(new_sock,buf,strlen(buf)+1);
                    printf("Please Wait...\n");
                }
            }
            else if(s == 0)
            {
                printf("client send over\n");
                close(new_sock);
                break;
            }
            else
            {
                break;
            }
        }
    }
    close(listen_sock);
    return 0;
}

代码的大致情况是,首先创建一个套接字listen_sock,用来监听链接的,而这个套接字创建出来后先要绑定IP地址还有端口号,因为是一个服务器,所以要有固定的IP和port,然后监听链接,当有链接请求时,listen_sock被accept函数调用获得,client方的IP和port,从而成功建立链接,返回一个服务型的新的套接字new_sock,通过new_sock进行服务器与客户端的通信。

//客户client端:

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


void usage(char* str)
{
    printf("usage:%s [local_addr] [local_port]\n",str);
}

int startup(char* ip,int port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        return 2;
    }

    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr = inet_addr(ip);

    if(connect(sock,(struct sockaddr*)&server,sizeof(server))<0)
    {
        perror("connect");
        return 3;
    }
    return sock;
}

int main(int argc,char* argv[])
{
    if(argc<3)
    {
        usage(argv[0]);
        exit(1);
    }

    int sock = startup(argv[1],atoi(argv[2]));

    printf("Connect to a server\n");

    while(1)
    {
        printf("Plesae Enter:");
        fflush(stdout);
        char buf[1024];
        fgets(buf,sizeof(buf),stdin);
        buf[sizeof(buf)-1] = 0;
        write(sock,buf,sizeof(buf));
        printf("Please wait ...\n");
        read(sock,buf,sizeof(buf));
        printf("server send:$ %s\n",buf);
    }
    close(sock);
    return 0;
}

与服务器端大致相同,因为client是发起连接请求的一端,所以client不需要绑定IP和port,也不需要进行监听,而获得一个链接,也就变成发起连接请求,建立连接成功后,就可以进行通讯了。

makefile:

.PHONY:all
all:server client
server:server.c
    gcc -o $@ $^
client:client.c
    gcc -o $@ $^
.PHONY:clean
clean:
    rm -f server client

多进程版本的服务器代码:

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


void usage(char* str)
{
    printf("usage:%s [local_addr] [local_port]\n",str);
}

int startup(char* ip,int port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(2);
    }

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port =htons( port);
    local.sin_addr.s_addr = inet_addr(ip);

    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        perror("bind");
        exit(3);
    }

    if(listen(sock,10)<0)
    {
        perror("listen");
        exit(4);
    }
    return sock;
}

int main(int argc,char* argv[])
{
    if(argc<3)
    {
        usage(argv[0]);
        exit(1);
    }

    int listen_sock = startup(argv[1],atoi(argv[2]));

    while(1)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);

        if(new_sock<0)
        {       
            perror("accept");
            continue;
        }
        printf("get a client: [%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));



        pid_t id=fork();
        if(id<0)
        {
            perror("fork");
            close(new_sock);
            continue;
        }
        else if(id == 0)
        {
            close(listen_sock);
            if(fork()>0)
            {
                exit(0);
            }
            else
            {
                char buf[1024];
                while(1)
                {
                    ssize_t s = read(new_sock,buf,sizeof(buf)-1);
                    if(s>0)
                    {
                        buf[s] = 0;
                        if(buf[0] != 0)
                        {
                            printf("client[%s,%d]:# %s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);
                            write(new_sock,buf,strlen(buf)+1);
                        }
                    }
                    else if(s == 0)
                    {
                        printf("client[%s:%d]send over\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
                        close(new_sock);
                        break;
                    }
                    else
                    {
                        close(new_sock);
                        break;
                    }

                }
                exit(5);
            }
        }
        else
        {
            close(new_sock);
            waitpid(id,NULL,0);
        }
    }
    close(listen_sock);
    return 0;
}

在单进程版本的基础上,在成功获取到一个连接后,使用fork函数创建子进程,让子进程去进行与客户端的通讯,因为子进程不需要监听,所以关闭listen_sock,父进程重新去获得一个新的连接,不需要进行与客户端的通信,因此关闭new_sock,但是由于子进程尚未退出,父进程需要进行等待,这样的话,事实上还是一个单进程的服务器,因此在子进程内部在fork一个子进程,让子进程的子进程去与客户端进行通讯,自己直接退出,这样父进程收到了子进程退出的信号,父进程就回去等待新的连接建立,而子进程的子进程,由于他的父进程直接退出,导致他变成孤儿进程,当他与客户端完成通信。他就会被操作系统回收。

多线程版本server服务器代码:

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



void usage(char* str)
{
    printf("usage:%s [local_addr] [local_port]\n",str);
}

int startup(char* ip,int port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(2);
    }

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port =htons( port);
    local.sin_addr.s_addr = inet_addr(ip);

    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        perror("bind");
        exit(3);
    }

    if(listen(sock,10)<0)
    {
        perror("listen");
        exit(4);
    }
    return sock;
}

void* handler_request(void* arg)
{
    int new_sock = *(int*)arg;
    char buf[1024];
    while(1)
    {

        ssize_t s = read(new_sock,buf,sizeof(buf)-1);
        if(s>0)
        {
            buf[s] = 0;
            if(buf[0] != 0)
            {
                printf("client:# %s\n",buf);
                write(new_sock,buf,strlen(buf)+1);
            }
        }
        else if(s == 0)
        {
            printf("client send over\n");
            close(new_sock);
            break;
        }
        else
        {
            close(new_sock);
            break;
        }

    }
    close(new_sock);
    return (void*)0;
}

int main(int argc,char* argv[])
{
    if(argc<3)
    {
        usage(argv[0]);
        exit(1);
    }

    int listen_sock = startup(argv[1],atoi(argv[2]));

    while(1)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);

        if(new_sock<0)
        {       
            perror("accept");
            continue;
        }
        printf("get a client: [%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));

//     多线程版本
        pthread_t fid;
        pthread_create(&fid,NULL,(void*)handler_request,&new_sock);
        pthread_detach(fid);

    }
    close(listen_sock);
    return 0;
}

多线程版本服务器,是利用现成的可分离性来实现,多人连接服务器的;

//makefile
server:server.c
    gcc -o $@ $^ -lptnread
.PHONY:clean
clean:
    rm -f server

因为server.c加入了线程的库,使用了线程接口,因此在编译时要链接线程静态的库;

多线程版本中出现了两个新的函数接口:

//创建新线程:
#include<pthread.h>
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine) (void *),void *arg);

参数:
thread:新创建启动的线程
start_routine:新建线程的启动例程;
arg:给启动历程传参;
返回值:成功返回0;失败返回错误码

//线程分离:
#include<pthread.h>
int pthread_detach(pthread_t thread);

参数:
thread:要分离的线程
返回值:成功返回0,失败返回错误码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值