套接字编程-TCP网络编程

套接字地址结构

进行套接字编程需要指定套接字的地址作为参数,例如bind()函数原型

int bind(int sockfd, /*套接字文件描述符*/
		 const struct sockaddr *myaddr, /*套接字地址结构*/
         socklen_t addrlen /*套接字地址结构长度*/
 );

不同的协议族有不同的地址结构定义方式。地址结构通常以sockaddr_开头,每一个协议族有一唯一的后缀,例如 以太网协议的地址结构为sockaddr_in,Netlink协议的地址结构为sockaddr_nl。

通用套接字地址数据结构

通用套接字地址结构可与不同协议族之间的地址结构进行强制转换,定义如下:

struct sockaddr{
	sa_family_t sa_family; /*协议族   sa_family_t类型为unsigned short*/
  	char        sa_data[14]; /*协议族数据*/
}
以太网协议的套接字地址数据结构
struct sockaddr_in{
	u8       sin_len;     /*结构struct sockaddr_in的长度 */
  	u8       sin_family;  /*协议族 通常为AF_INET 表示IP*/
  	u16      sin_port;    /*16位的端口号, 网络字节序*/
  	struct in_addr  sin_addr;  /*IP地址32位*/
  	char     sin_zero[8];      /*未用*/
}

struct in_addr的成员变量用于表示IP地址,定义如下:

struct in_addr{
  	u32 s_addr;   /*32位IP地址,网络字节序(大端存储)*/
}

结构struct sockaddr 和sockaddr_in的关系图如下:

在这里插入图片描述

由于二者结构大小完全一致,在进行套接字编程时,通常利用struct sockaddr_in进行设置,然后强制转换为结构struct sockaddr类型。

Netlink协议套接字地址结构
struct sockaddr_nl {
     __kernel_sa_family_t    nl_family;  /* netlink协议族类型 一般设置为AF_NETLINK (跟AF_INET对应)*/
     unsigned short  nl_pad;     /* 填充zero */
     __u32       nl_pid;     /* port ID  (通信端口号)*/
     __u32       nl_groups;  /* 组播 multicast groups mask */
 };
TCP网络编程

TCP网络编程存在两种模式

客户端模式:根据目的服务器的地址和端口进行连接, 向服务器发送请求并对服务器的响应进行数据处理

服务器模式:创建服务程序,等待客户端的连接请求,接收到用户的连接请求后,根据用户的请求进行处理。

客服端和服务器端TCP网络编程流程如图:

在这里插入图片描述

套接字初始化socket()

​ 套接字初始化过程中,根据用户对套接字的需求来确定套接字的选项。这个过程中的函数为socket(),它按照用户定义的网络类型、协议类型和具体的协议标号等参数来定义。系统根据用户的需求生成一个套接字文件描述符供用户使用。

函数原型如下:

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domin, int type, int protocol);
/*
domin 套接字通信的协议族
type  套接字通信的协议类型
protocol 指定某个协议的特定类型,如果某个协议只有一种类型,protocol设置为0
*/
/*TCP例子*/
int mysockfd = socket(AF_INET, SOCK_STREAM, 0);

​ 该函数建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符。如果函数调用成功,会返回一个表示这个套接字的文件描述符,失败的时候返回-1。

domain

​ 参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义。以太网中应该PF_INET 这个域。在程序设计的时候会发现有的代码使用了AF_INET 这个值,在头文件 中AF_INET 和PF_INET的值是一致的。

在这里插入图片描述

type

​ 参数type用于设置套接字通信的类型,主要有SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据包套接字)等。
在这里插入图片描述

并不是所有的协议族都实现了这些协议类型,例如,AF_INET 协议族就没有实现 SOCK_SEQPACKET 协议类型。

​ type类型为SOCK_STREAM的套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用connect()函数进行。一旦连接,可以使用read()或者write()函数进行数据的传输。流式通信方式保证数据不会丢失或者重复接收,当数据在一段时间内仍然没有接收完毕,可以将这个连接认为已经死掉。

​ type类型为 SOCK_DGRAM和SOCK_RAW这两种套接字可以使用函数sendto()来发送数据,使用recvfrom()函数接收数据, recvfrom()接收来自指定IP地址的发送方的数据。

​ type类型为SOCK_PACKET是一种专用的数据包,它直接从设备驱动接收数据。

protocol

​ 参数protocol用于指定某个协议的特定类型,即type类型中的某个类型。通常某个协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。

errno

​ 函数socket()并不总是执行成功,有可能会出现错误,错误的产生有多种原因,可以通过errno获得,具体值和含义如下表。通常情况下造成函数socket()失败的原因是输入的参数错误造成的,例如某个协议不存在等,这时需要详细检查函数的输入参数。由于函数的调用不一定成功,在进行程序设计的时候,一定要检查返回值。

在这里插入图片描述

应用层socket与内核函数的关系(系统调用)

在这里插入图片描述

图中用户调用函数sock=socket(AF_INET,SOCK_STREAM,0),这个函数会调用系统调 用函数sys_socket(AF_INET, SOCK_STREAM,0)(在文件net/socket.c中)。系统调用sys_socket()分为两部分,一部分生成内核socket结构(注意与应用层的socket函数是不同的),另一部分与文件描述符绑定,将绑定的文件描述符值传给应用层。内核sock结构如下(在文件linux/net.h中) :

 struct socket{
 				socket_state  state; /*socket 状态(例 SS_CONNECTED 等) */
 				unsigned long flags; /*socket 标志(SOCK_ASYNC_NOSPACE 等) */
 				const struct proto_ops *ops; /*协议特定的socket操作*/
 				struct fasync_struct *fasync_list; /*异步唤醒列表*/
 				struct file *file;   /*文件指针*/
 				struct sock *sk;     /*内部网络协议结构*/
 				wait_queue_head_t wait;   /*多用户时的等待队列*/
 				short type;  /*socket类型(SOCK STREAM等) */
 };

​ 内核函数sock_create()根据用户的domain指定的协议族,创建一个内核socket结构绑定到当前的进程上,其中的type与用户空间用户的设置值是相同的。

​ sock_map_fd()函数将socket结构与文件描述符列表中的某个文件描述符绑定。之后文件描述符对应该socket结构。

套接字地址绑定bind()

​ 在建立套接字文件描述符成功后,需要对套接字进行地址绑定,将套接字与一个地址结构进行绑定。包括IP地址、端口地址及协议类型等参数。 bind()函数将长度为addlen的struct sockadd类型的参数my_addr与sockfd绑定在一起,将sockfd绑定到某个端口上,如果使用connect()函数则没有绑定的必要。

函数原型如下:

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

​ 参数sockfd是用socket()函数创建的文件描述符。

​ 参数my_addr是指向一个结构为sockaddr参数的指针, sockaddr中包含了地址、端口和IP地址的信息。在进行地址绑定的时候,需要先将地址结构中的IP地址、端口、类型等进行设置后才能进行绑定,绑定后才能将套接字文件描述符与地址等结合在一起。
参数addrlen是my_addr结构的长度,可以设置成 sizeof(struct sockaddr)。使用 sizeof(struct sockaddr)来设置addlen是一个良好的习惯,虽然一般情况下使用AF_INET来设置套接字的类型和其对应的结构,但是不同类型的套接字有不同的地址描述结构,如果对地址长度进行了强制的指定,可能会造成不可预料的结果。

bind()函数的返回值为0时表示绑定成功,-1表示绑定失败, errno的错误值如下图所示

在这里插入图片描述

应用层socket与内核函数的关系(系统调用)

在这里插入图片描述

函数bind()是应用层函数,要使函数生效,需要将相关的参数传递给内核并进行处理。应用层的bind()函数与内核层之间的函数过程如上图所示,图中是一个AF_INET族函数进行绑定的调用过程。

应用层的函数bind(sockfd,(struct sockaddr*)&my_addr, sizeof(struct sockaddr))调用系统函数过程sys_bind(sockfd,(struct sockaddr*)&my_addr, sizeof(struct sockaddr))。sys_bind()函数首先调用函数sockfd_lookup_light()来获得文件描述符sockfd对应的内核struct sock结构变量,然后调用函数move_addr_to_kernel()将应用层的参数my_addr复制进内核,放到address变量中。内核的sock结构是在socket()函数时根据协议生成的,它绑定了不同协议族的bind() 函数的实现方法,在AF_INET族中的实现函数为inet_bind(),即会调用AF_INET族的bind()函数进行绑定处理。

服务器侦听listen()

​ 由于一个服务器需要满足多个客户端的连接请求,而服务器在某个时间仅能处理有限个数的客户端连接请求,所以服务器需要设置服务端排队队列的长度。服务器侦听连接会设置这个参数,限制客户端中等待服务器处理连接请求的队列长度。在客户端发送连接请求之后,服务器需要接收客户端的连接,然后才能进行其他的处理。

函数原型如下:

#include<sys/socket.h>
int listen(int sockfd, int backlog);
/*
sockfd 为socket()创建的套接字文件描述符
backlog表示等待队列的长度
运行成功返回0,运行失败返回-1,并设置errno值 错误代码含义如下图
*/

在这里插入图片描述

在接受一个连接之前,需要用listen()函数来侦听端口,listen()函数中参数backlog的参数表示在accept()函数处理之前在等待队列中的客户端的长度,如果超过这个长度,客户 端会返回一个ECONNREFUSED错误。

listen()函数仅对类型为SOCK_STREAM或者SOCK_SEQPACKET的协议有效,例如, 如果对一个SOCK_DGRAM的协议使用listen(),将会出现错误 errno应该为值EOPNOTSUPP,表示此socket不支持listen()操作。大多数系统的设置为20,可以将其设置修改为5或者10,根据系统可承受负载或者应用程序的需求来确定。

应用层listen()与内核函数的关系(系统调用)

应用层listen()和内核层listen()的关系如图所示,应用层的listen()函数对应于系统 调用sys_listen(O)函数。sys_listen()函数首先调用sockfd_lookup_light()函数获得sockfd对应的内核结构struct socket,查看用户的backlog设置值是否过大,如果过大则设置为系统默 认最大设置。然后调用抽象的listen()函数,这里指的是AF_INET 的listen()函数inet_listen()。inet_listen()函数首先判断是否合法的协议族和协议类型,然后更新socket的状态值为TCP_LISTEN,然后为客户端的等待队列申请空间并设定侦听端口。

在这里插入图片描述

接收网络请求accept()

​ 当一个客户端的连接请求到达服务器主机侦听的端口时,此时客户端的连接会在队列中等待,直到使用服务器处理接收请求。函数accept()成功执行后,会返回一个新的套接字文件描述符来表示客户端的连接,客户端连接的信息可以通过这个新描述符来获得。因此当服务器成功处理客户端的请求连接后,会有两个文件描述符,老的文件描述符表示正在监听的socket,新产生的文件描述符表示客户端的连接,函数send()和recv()通过新的文件描述符进行数据收发。

函数原型:

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

通过accept()函数可以得到成功连接客户端的IP地址、端口和协议族等信息,这个信息是通过参数addr获得的。当accept()返回的时候,会将客户端的信息存储在参数addr中。 参数addrlen表示第2个参数(addr)所指内容的长度,可以使用sizeof(struct sockaddr_in)来获得。需要注意的是在accept 中addrlen参数是一个指针而不是结构, accept0将这个指针传给TCP/IP协议栈。

accpet()函数的返回值是新连接的客户端套接字文件描述符,与客户端之间的通信是通过accept()返回的新套接字文件描述符来进行的,而不是通过建立套接字时的文件描述符,这是在程序设计的时候需要注意的地方。如果accept()函数发生错误, accept()会返回-1。错误类型如图所示:

在这里插入图片描述

应用层accept()函数与内核函数的关系(系统调用)

​ 应用层的accept()函数对应内核层的sys_accept()系统调用函数。函数sys_accept()查找文件描述符对应的内核socket结构、申请一个用于保存客户端连接的新的内核socket结构、执行内核接受函数、获得客户端的地址信息、将连接的客户端地址信息复制到应用层的用户、返回连接客户端socket对应的文件描述符。
sys_accept()调用函数 sockfd_lookup_light()查找到文件描述符对应的内核socket结构后,会申请一块内存用于保存连接成功的客户端的状态。socket结构的一些参数,例如类型type、操作方式ops等会继承服务器原来的值,例如如果原来服务器的类型为AF_INET则,其操作模式仍然是af_inet.c文件中的各个函数。然后会查找文件描述符表,获得一个新结构对应的文件描述符。
​ accept()函数的实际调用根据不同的协议族是不同的,即函数指针 sock->ops->accept要有socket()初始化时的协议族而确定。当为AF_INET时,此函数指针对应于af_inet.c文件中的inet_accept()函数。当客户端连接成功后,内核准备连接的客户端的相关信息,包含客户端的IP地址、客户端的端口等信息,协议族的值继承原服务器的值。在成功获得信息之后会调用move_addr_to_userO函数将信息复制到应用层空间,具体的地址由用户传入的参数来确定。
在这里插入图片描述

连接网络服务器connect()

​ 客户端在建立套接字之后,不需要进行地址绑定,直接连接网络服务器。connect()函数原型如下:

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

​ 参数sockfd是建立套接字时返回的套接字文件描述符,它是由系统调用socket()返回的。参数serv_addr是一个指向数据结构sockaddr的指针,其中包括客户端需要连接的服务器的目的端口和IP地址以及协议类型。参数addrlen 表示第二个参数内容的大小,可以使用sizeof(struct sockaddr)获得。connect()函数的返回值在成功时为0,当发生错误的时候返回-1,可以查看errno获得错误的原因,错误值如下图。

在这里插入图片描述

应用层connect()函数和内核函数之间的关系

在这里插入图片描述

写入数据函数write()

​ 当服务器端在接收到一个客户端的连接后,可以通过套接字描述符进行数据的写入操作。对套接字进行写入的形式和过程与普通文件的操作方式一致,内核会根据文件描述符的值来查找所对应的属性,当为套接字的时候,会调用相对应的内核函数。

函数原型

#include <unistd.h>
ssize_t write (int fd,const void *buf,size_t count)

下面是一个向套接字文件描述符中写入数据的例子,将缓冲区data的数据全部写入套接字文件描述符s中,返回值为成功写入的数据长度。

int size;
char data[1024];
size = write(s, data, 1024);
读取数据函数read()

​ 与写入数据类似,使用read()函数可以从套接字描述符中读取数据。当然在读取数据之前,必须建立套接字并连接。

函数原型

#include <unistd.h>
ssize_t read(int fd, void *buf ,size_t count)

​ 读取数据的方式如下所示,从套接字描述符s中读取1024个字节,放入缓冲区data中, size变量的值为成功读取的数据大小。

int size;
char data[1024];
size = read(s, data, 1024);
关闭套接字数close()/shutdown()

​ 当服务器处理完数据,要结束与客户端的通信过程的时候,需要关闭套接字连接,内核释放相关的资源。

#include <unistd.h>
int close(int sockfd)

​ 函数shutdown()可以使用更多方式来关闭连接,允许单方向切断通信或者切断双方的通信。

​ 函数原型如下,第一个参数s是切断通信的套接口文件描述符,第二个参数how表示切断的方式。

 #include <sys/socket.h>
 int shutdown (int sockfd, int how);

​ 函数shutdown()用于关闭双向连接的一部分,具体的关闭行为方式通过参数的how设置来实现。可以为如下值:

​ SHUT_RD:值为0,表示切断读,之后不能使用此文件描述符进行读操作。

​ SHUT_WR:值为1,表示切断写,之后不能使用此文件描述符进行写操作。

​ SHUT_RDWR:值为2,表示切断读写,之后不能使用此文件描述符进行读写操作,与close)函数功能相同。

​ 函数shutdown()如果调用成功则返回0,如果失败则返回-1,通过errno可以获得错误的具体信息,错误值含义如下图:

在这里插入图片描述

基于TCP协议的客户器/服务端例子

​ 程序分为服务器端和客户端,客户端连接服务器后从标准输入读取输入的字符串,发送给服务器:服务器接收到字符串后,发送接收到的总字符串个数给客户端;客户端将接收到的服务器的信息打印到标准输出。

服务器网络程序
1.初始化工作
#include<stdio.h>
#include<stdlib.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<Linux/in.h>
#define SERV_PORT 4567    /*服务器侦听端口地址*/
#define BACKLOG 	5				/*侦听队列长度*/
int main(int argc, char *argv[]){
 	int client_fd, server_fd;       		/*client_fd为客户端的socket描述符,server_fd为服务器的socket描述符*/
  	struct sockaddr_in server_addr;			/*服务器套接字地址结构*/
  	struct sockaddr_in client_addr;  		/*客户端套接字地址结构*/
  	int ret;														/*返回值*/
  	pid_t pid;													/*分叉进程ID*/
}
2.建立套接字
server_fd = socket(PF_INET, SOCK_STREAM, 0); /*建立流式套接字*/
if(server_fd < 0){
  	printf("socket error\n");
  	return -1;
}
3.设置服务器套接字地址参数
/*设置服务器套接字地址参数*/
bzero(&server_addr, sizeof(server_addr));					/*清零*/
server_addr.sin_family = AF_INET;									/*设置地址族*/
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);		/*本地地址*/
server_addr.sin_port = htons(SERV_PORT);    				/*服务器端口*/
4.绑定套接字地址到套接字文件描述符
ret = bind(server_fd, (struct  sockaddr*)&server_addr, sizeof(server_addr)); /*绑定地址到套接字描述符*/
if(ret < 0){
  printf("bind error!\n");
  return -1;
}
5.设置侦听队列
ret = listen(server_fd, BACKLOG); 	/*设置侦听队列*/
if(ret < 0){
  printf("listen error!\n");
  return -1;
}
6.主循环过程

​ 在主循环过程中为了方便处理,每个客户端的连接请求服务器会分叉一个进程进行处理。函数fork()出来的进程继承了父进程的属性,例如套接字描述符,在子进程和父进程中都有一套。为了防止误操作,在父进程中关闭了客户端的套接字描述符,在子进程中关闭了父进程中的侦听套接字描述符。一个进程中的套接字文件描述符的关闭,不会造成套接字的真正关闭,因为仍然有一个进程在使用这些套接字描述符,只有所有的进程都关闭了这些描述符, Linux内核才释放它们。在子进程中,处理过程通过调用函数 process_conn_server()来完成。

for(;;){
        int addrlen = sizeof(struct sockaddr);
        client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addrlen);			/*接收客服端连接*/
        if(client_fd < 0){ 																	/*出错*/
                continue;																		/*结束本次循环*/
        }
        pid = fork();																				/*建立子进程处理该连接*/
        if(pid == 0){
            close(server_fd);																/*子进程中关闭服务器的的侦听*/
            process_conn_server(client_fd);	                /*处理连接*/
            exit(0);                                        /*退出子线程*/											
        }else{  
            close(client_fd);																/*在父进程中关闭客户端的连接*/
        }
}
7.服务器读取和显示字符串

​ 服务器端对客户端连接的处理过程如下,先读取从客户端发送来的数据,然后将接收到的数据个数发送给客户端。

void process_conn_server(int client_fd){   												/*服务器建立子进程处理客户端请求*/
  	ssize_t size = 0;
  	char buffer[1024];																						/*数据缓冲区*/
  	for(;;){
      	size = read(client_fd, buffer, 1024);											/*从套接字中读取数据放到缓冲区buffer中*/
      	if(size == 0){																						/*没有数据,准备结束该子线程*/
          	return;
        }
      	sprintf(buffer, "%d bytes altogether!\n", size);          /*构建响应字符,为接收到客户端字节的数量*/
      	write(client_fd, buffer, strlen(buffer) + 1);							/*发给客户端*/
    }
}
8.注册信号函数,防止产生僵尸进程
void singal_handle(int sign){
    wait(NULL);
}

signal(SIGCHLD, singal_handle); /*放到main函数中 ,用于接收子进程退出信号SIGCHID,执行信号处理函数singal_handle,调用wait 回收子线程*/

信号是发生某件事情时的一个通知,有时候也将称其为软中断。信号将事件发送给相关的进程,相关进程可以对信号进行捕捉并处理。信号的捕捉由系统自动完成,信号处理函数的注册通过函数signal()完成。 这个函数向信号signum注册一个void(*sighandler_t)(int)类型的函数,函数的句柄为handler。当进程中捕捉到注册的信号时,会调用响应函数的句柄handler。信号处理函数在处理系统默认的函数之前会被调用。

信号处理函数原型:

#include<signal.h>
typedef void (*sighandler_t)(int);   /*函数指针 参数为int 返回值为void*/
sighandler_t signal(int signum, sighandler_t handler);

signum信号量:


#define SIGHUP		1  //hang up 挂断控制终端或进程
#define SIGINT		2  //interrupt 来自键盘的中断
#define SIGQUIE		3  //quit	退出
#define SIGILL		4  //illeagle 非法指令
#define SIGTRAP  	5  //trap   跟踪断点
#define SIGABORT	6  //Abort  异常结束
#define SIGIOT		6  //IO trap  异常   
#define SIGUNUSED	7  //Unused 没有使用
#define SIGFPE		8  //FPE 协处理器出错
#define SIGKILL		9  //kill  强迫终止
#define SIGUSR1		10 //use1  用户信号1,进程可使用
#define SIGSEGV		11 //segment violation 无效内存引用
#define SIGUSR2		12 //user2  用户信号2,进程可使用
#define SIGPIPE		13 //pipe  管道写出错
#define SIGALRM		14 //alarm  实时定时器报警
#define SIGTERM		15 //terminate 进程终止
#define SIGSTKFLT	16 //stack Fault 栈出错
#define SIGCHLD		17 //child 子进程停止
#define SIGCONT		18 //continue 回复进程继续
#define SIGSTOP		19 //stop  停止进程执行
#define SIGTSTP		20 //tty stop tty发出停止进程
#define SIGTTIN		21 //tty in  后台进程请求输入
#define SIGTTOU		22 //tty out 后台进程请求输出
 
#define SA_NOCLDSTOP 1 //进程处于停止状态,就不对sigchild进行处理
#define SA_NOMASK	 0X40000000
//不阻止在指定的信号处理程序中再收到该信号
#define SA_ONESHOT 0X80000000
//信号句柄一旦被调用就恢复到默认处理句柄
#define SIG_BLOCK  //在阻塞信号集中加上给定的信号集
#define SIG_UNBLOCK//从阻塞信号集中删除指定的信号集
#define SIG_SETMASK//设置阻塞信号集(信号屏蔽码)
 
#define SIG_DFL 	((void(*)(int))0) //默认信号处理程序
#define SIG_IGN		((void (*)(int))1)//忽略信号处理程序

客户端的网络程序
#include<stdio.h>
#include<stdlib.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include "tcp_process.h"
#define SERV_PORT 4567    /*服务器侦听端口地址*/
int main(int argc, char *argv[]){
    int sockfd;                          /*socket描述符*/
    struct sockaddr_in server_addr;			 /*服务器地址结构*/
    int ret;														 /*返回值*/
    
    sockfd = socket(PF_INET, SOCK_STREAM, 0); /*建立流式套接字*/
    if(sockfd < 0){
        printf("socket error!\n");
        return -1;
    }
    
    bzero(&server_addr, sizeof(server_addr));								/*清零*/
    server_addr.sin_family = AF_INET;												/*设置地址族*/
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");		/*服务器地址*/
    server_addr.sin_port = htons(SERV_PORT);    						/*服务器端口*/
    
    connect(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr));  /*连接服务器*/
    process_conn_client(sockfd);																							 /*客服端处理过程*/
    close(sockfd);
}	
客户端读取和显示字符串

客户端从标准输入读取数据到缓冲区buffer中,发送到服务器端。然后从服务器端读取服务器的响应,将数据发送到标准输出。

void process_conn_client(int sockfd){
  	ssize_t size = 0;
  	char buffer[1024];
  	
  	for(;;){
      	size = read(0, buffer, 1024);       /*从标准输入中读取数据放到缓冲区buffer中, 0代表标准输入文件描述符*/
		buffer[size - 1] = '\0';								/*删除换行符*/
		size = strlen(buffer);									/*真实标准输入大小,不包括换行符(回车键)*/
		if(size == 0){													/*标准输入仅包含换行符(回车键)直接退出*/
			write(sockfd, buffer, size); 
			break;
		} 
		if(size > 0){
          	write(sockfd, buffer, size);        /*发送服务器*/
          	size = read(sockfd, buffer, 1024);  /*从服务器读取数据*/
          	write(1, buffer, size);							/*写到标准输出, 1为标准输出文件描述符*/
        }
    }
}
makefile文件编写
all:client server    																#all规则,它依赖于client和server规则
client: tcp_process.o tcp_client.o									#client规则,生成客户端可执行程序
	gcc -o client tcp_process.o tcp_client.o
server: tcp_process.o tcp_server.o									#server规则,生成服务器端可执行程序
	gcc -o server tcp_process.o tcp_server.o				
clean:																							#清理规则,删除client、server和中间文件
	rm -rf  client server *.o
运行结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

仅输入回车键,关闭client1

在这里插入图片描述
在这里插入图片描述

参考文献 :Linux 网络编程

代码地址

github传送

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值