TCP服务器/客户端的Linux下的代码实现,单向通信、双向通信

本文详细讲解了服务器与客户端之间的TCP通信流程,包括服务器的socket()到close()操作,以及客户端的connect()和数据交互。通过实例演示了字符串长度响应、时间服务器和文件下载功能。同时提到了如何使用多线程实现双向通信,以实现实时数据交换。

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

tcp流程

服务器:socket() --> bind() --> listen() --> accept() --> read/write or recv/send–>close()
客户端:socket() --> connect() --> read/write or recv/send -->close()
在这里插入图片描述

测试例子:

1.客户端给服务器发送一个字符串,服务器返回给客户端这个字符串的长度 。
2.实现一个时间服务器,客户端发送time,服务器返回当前时间。
3.如果客户端发送get 1.txt的请求,服务器获取文件内容后,发送给客户端 。

服务器:

/*===============================================
 *   文件名称:server.c
 *   创 建 者:   xm  
 *   创建日期:2022年08月16日
 *   描    述:
 ================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
void fun_work1(char *str,int connfd);//例子1
void fun_work2(int connfd);//例子2
int fun_work3(int connfd);//例子3
int main(int argc, char *argv[])
{ 
    int sockfd=socket(PF_INET,SOCK_STREAM,0);
    if(sockfd<0)
    {
        perror("socket");
        return -1;
    }

    //端口复用函数,为解决端口号被系统占用的情况(如由强制关闭服务器引起)
    int on=1;
    int k=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
    if(k==-1)
    {
        perror("setsockopt");
        return -1;
    }

    struct sockaddr_in server_addr={0};
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(9000);
    //server_addr.sin_addr.s_addr=inet_addr("0.0.0.0");//能匹配本机所有ip地址
    server_addr.sin_addr.s_addr=htonl(INADDR_ANY);//也可以用宏,相当于0.0.0.0
    int len_ser=(int)sizeof(server_addr);
    if(bind(sockfd,(struct sockaddr*)&server_addr,len_ser)==-1)
    {
        perror("bind");
        return -1;
    }

    if(listen(sockfd,10)==-1)
    {
        perror("listen");
        return -1;
    }

    struct sockaddr_in client_addr={0};
    int len_cli=sizeof(client_addr);
    while(1)//循环服务器,解决客户端强制关闭导致服务器死循环或者直接退出的情况
    {		
    	printf("wait a client....\n");
        int connfd=accept(sockfd,(struct sockaddr*)&client_addr,&len_cli);
        if(connfd<0)
        {
            perror("accept");
            return -1;
        }

        printf("client ip:%s  client port:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));

        //收发数据
        char buf[64]={0};
        while(1)
        {

            int m=read(connfd,buf,64);
            if(m<0)
            {
                perror("read");
                return -1;
            }
            else if(m==0)//客户端关闭,客户端用ctrl+c强制退出也算关闭
            {
             	close(connfd);
                break;
            }
            printf(">%s\n",buf);
            int ret;

            if(strcmp(buf,"time")==0)//例子2
            {
                fun_work2(connfd);
            }
            else if(strcmp(buf,"get 1.txt")==0)//例子3
            {
                ret=fun_work3(connfd);
            }
            else
            {
                fun_work1(buf,connfd);//例子1
            }
            
            memset(buf,0,64);
        }
    }
    close(sockfd);
   
    return 0;   
} 
void fun_work1(char *buf,int connfd)
{
    char *str=buf;
    sprintf(str,"%ld",strlen(str));
    write(connfd,str,strlen(str));
}
void fun_work2(int connfd)
{
    char buf[64]={0};
    time_t mytime;
    time(&mytime);
    char *str=ctime(&mytime);
    strcpy(buf,str);
    write(connfd,buf,strlen(buf));

}
int fun_work3(int connfd)
{
    int cp_fd=open("./../1.txt",O_RDONLY);
    if(cp_fd<0)
    {
        perror("open");
        return -1;
    }
    int m,ret;
    char buf[64]={0};
    while((m=read(cp_fd,buf,64))>0)
    {
        ret=write(connfd,buf,m);
        if(ret<0)
        {
            perror("write");
            return -1;
        }
        fputs(buf,stdout);

    }
    printf("获取1.txt文件成功\n");
    close(cp_fd);


}

客户端:

/*===============================================
*   文件名称:client.c
*   创 建 者:   xm  
*   创建日期:2022年08月16日
*   描    述:
================================================*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
int client_work1(int sockfd);//例子1
int  client_work2(int sockfd);//例子2
int  client_work3(int sockfd);//例子3
int main(int argc, char *argv[])
{ 
	int sockfd=socket(PF_INET,SOCK_STREAM,0);//创建套接字
	if(sockfd<0)
	{
		perror("socket");
		return -1;
	}
	
	struct sockaddr_in client_addr={0};
	client_addr.sin_family=AF_INET;
	client_addr.sin_port=htons(9000);//要连接的服务器的端口号
	//client_addr.sin_addr.s_addr=inet_addr("0.0.0.0");//能匹配本机所有ip地址
	client_addr.sin_addr.s_addr=htonl(INADDR_ANY);//也可以用宏,相当于0.0.0.0
	
	int len_cli=(int)sizeof(client_addr);
	if(connect(sockfd,(struct sockaddr*)&client_addr,len_cli)==-1)
	{
		perror("connect");
		return -1;
	}
	
	
	printf("client ip:%s  client port:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
	
	//收发数据
	char buf[64]={0};
	while(1)
	{
		printf("\n--------特殊功能关键字--------\n");
		printf("----quit---- 关闭客户端\n");
		printf("----time---- 查看当前时间\n");
		printf("----get 1.txt---- 下载文件测试\n");
		printf("input:\n");		
		fgets(buf,64,stdin);
		buf[strlen(buf)-1]='\0';//去掉换行符
		int m=write(sockfd,buf,64);
		if(m<0)
		{
			perror("write");
			return -1;
		}
		else if(strcmp(buf,"quit")==0)//客户端关闭
		{	
			break;
		}
		
		

		
		if(strcmp(buf,"time")==0)
		{
			if(client_work2(sockfd)<0)//例子2
			{
				perror("work2");
				return -1;
			}
		}
		else if(strcmp(buf,"get 1.txt")==0)//例子3
		{
			if(client_work3(sockfd)<0)
			{
				perror("work3");
				return -1;
			}
		}
		else
		{
			if(client_work1(sockfd)<0)//例子1
			{
				perror("work1");
				return -1;
			}
		}
		memset(buf,0,64);
		
	}
	close(sockfd);
	
    return 0;       
} 
int  client_work1(int sockfd)
{
		char buf[64];
		int m=read(sockfd,buf,64);
		if(m<0)
		{
			perror("read");
			return -1;
		}
		printf("字符串长度 %s\n",buf);
		return 0;
}
int  client_work2(int sockfd)
{
		char buf[64];
		int m=read(sockfd,buf,64);
		if(m<0)
		{
			perror("read");
			return -1;
		}
		printf("时间 %s\n",buf);
		return 0;
}
int  client_work3(int sockfd)
{
		
		char buf[64]={0};
		int cp_fd=open("1.txt",O_RDWR|O_CREAT,0664);
		if(cp_fd<0)
		{
			perror("open");
			return -1;
		}
		
		int m=-1;
		while((m=read(sockfd, buf, 64))>0)
		{		
						
			write(cp_fd,buf,64);
			memset(buf,0,64);
			if(m<64)break;
		}
		
		printf("获取1.txt文件成功\n");
		close(cp_fd);
		return 0;
}

上述的程序并不能实现真正的双向通信,因为服务器的接收和发送是按照严格顺序进行,在接收数据阻塞时,无法实现服务器向客户端发送功能,客户端也是同理。为了能够让数据收发之间互不影响,实现双向通信的TCP客户/服务器,就需要借助多进程或者多线程的方式。以下为采用多线程的方式实现双向通信:

服务器:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
void *send_data(void *arg);
int main(int argc, const char *argv[])
{
    //1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    }

    //端口复用函数,为解决端口号被系统占用的情况(如由强制关闭服务器引起)
    int on=1;
    int k=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
    if(k==-1)
    {
        perror("setsockopt");
        return -1;
    }

    //2. 绑定服务器的IP地址和端口号
    struct sockaddr_in serveraddr = {0};
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(9999);
    //serveraddr.sin_addr.s_addr = inet_addr("0");
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);

    int len = sizeof(serveraddr);

    int ret = bind(sockfd,  (struct sockaddr *)&serveraddr, len );
    if(ret == -1)
    {
        perror("bind");
        return -1;
    }

    //3.设置监听套接字
    ret = listen(sockfd, 10);
    if(ret == -1)
    {
        perror("listen");
        return -1;
    }

    //4.等待客户端连接, 返回一个新的套接字(connfd:收发数据)
    //accept(sockfd, NULL, NULL);

    struct sockaddr_in clientaddr = {0};

    int n = sizeof(clientaddr);
    while(1)//循环服务器,解决客户端关闭导致服务器死循环或者直接退出的情况
    {
        printf("wait a client....\n");
        int connfd = accept(sockfd, (struct sockaddr *)&clientaddr, &n);
        if(connfd < 0)
        {
            perror("accept");
            return -1;
        }

        printf("client ip:%s client port:%d\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

        pthread_t tid;
        pthread_create(&tid,NULL,send_data,(void*)&connfd);//创建子线程,用于数据发送
        pthread_detach(tid);
        //收发数据
        char buf[64] = {0};

        while(1)
        {
            int m = recv(connfd, buf, 64,0);
            if(m < 0)
            {
                perror("read");
                return -1;
            }
            else if(m == 0) //客户端关闭
            {
                close(connfd);
                break;
            }

            printf("message:%s\n", buf);

            memset(buf, 0, 64); //数组清零
        }
    }

    close(sockfd);


    return 0;
}
void *send_data(void *arg)//服务器发送数据--子线程
{
    int nfd=*(int*)arg;
    char buf[1024]={0};
    while(1)
    {
        fgets(buf,1024,stdin);
        buf[strlen(buf)-1]='\0';
        send(nfd,buf,strlen(buf),0);
    }
    close(nfd);
}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
void *recv_data(void *arg);
int main(int argc, const char *argv[])
{
    //1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    }

    //2. 连接服务器(IP,PORT)
    struct sockaddr_in serveraddr = {0};
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(9999);
    serveraddr.sin_addr.s_addr = inet_addr("0");
    //serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    int len = sizeof(serveraddr);

    int ret = connect(sockfd,  (struct sockaddr *)&serveraddr, len );
    if(ret == -1)
    {
        perror("connect");
        return -1;
    }

    pthread_t tid;
    pthread_create(&tid,NULL,recv_data,(void*)&sockfd);//创建子线程,用于数据接收
    pthread_detach(tid);
    
    char buf[64] = {0};

    while(1)
    {
        fgets(buf,64,stdin);
        buf[strlen(buf)-1]='\0';
        send(sockfd, buf, strlen(buf),0);

        memset(buf, 0, 64); //数组清零
    }

    close(sockfd);

    return 0;
}
void *recv_data(void *arg)
{
    int sockfd=*(int*)arg;
    char buf[1024]={0};
    while(1)
    {
        int n=recv(sockfd,buf,sizeof(buf),0);
        if(n<=0)
        {
            perror("read");
            return NULL;
        }
        printf("%s\n",buf);
        memset(buf,0,sizeof(buf));
    }
}

注意在运行可执行文件时要链接pthread库

例如:

gcc tcp_server.c -lpthread -o tcp_ser

gcc tcp_client.c -lpthread -o tcp_cli

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值