Linux网络编程(Linux网络编程实例)

Linux网络编程 Linux网络编程 1.网络基础2.TCP编程1.socket函数2.bind3.listen4.accept 3.并发服务器1.多线程服务器2.多进程服务器 4.TCP的API补充1.send()函数2.recv()

Linux网络编程

1. 网络基础知识2. TCP 编程1. 套接字函数2. 绑定3. 监听4. 接受

3. 并发服务器1. 多线程服务器2. 多进程服务器

4.TCP API补充1.send()函数2.recv()

5.UDP编程6.IO复用1.IO模型2.模型选择: 3.select_client.c4.select_server.c

7. TCP 3次握手和4次挥手(面试重要问题)

1.网络基础

网络互连促成了TCP/IP 协议的创建。早期版本的阿帕网不允许不同类型的计算机或不同类型的操作系统互连,也不具备纠错功能。 1974 年,第一个TCP 协议的详细描述发布。

TCP协议分为两种不同的协议。

传输控制协议(TCP),用于检测网络传输中的错误,以及网际协议(IP),专门负责互连不同的网络。从此,TCP/IP协议诞生了。

TCP/IP 协议已成为互联网的“世界语”。

网络架构

网络的设计采用分而治之的方法,将网络功能划分为不同的模块,并以分层的方式有机地将它们组合起来。每层实现不同的功能,其内部实现方法对其他外部层是透明的。每一层都向上提供服务,利用下面各层提供的服务。网络体系结构是指网络的分层结构以及每层使用的协议集。两种非常重要的体系结构类型:OSI 和TCP/IP。

OSI模型有七层:应用层、表示层、会话层、传输层、网络层、数据链路层和物理层。 OSI模型是一个理想化的模型,尚未完全实现。尽管与OSI 模型相关的协议很少使用,但该模型本身却非常通用。

TCP/IP 协议簇系统:TCP/IP 是互联网的行业标准,有四层:

应用层Relnet、FTP、HTTP、DNS、SMTP、其他传输层TCP 和UDP 网络层IP、ICM 和IGMP、端到端传输网络接口和物理层以太网、令牌环网络、FDDI、Wi-Fi、GPS /2G/3G/4G

上网准备知识

1.插座

它是一个编程接口和专用文件描述符(执行IO操作功能,例如读、写、关闭等),并且不限于TCP/IP协议,而是用于TCP连接和无连接。 UDP。套接字代表网络编程中的一种资源分类。

1. 流式套接字(SOCK_STREAM)。对应于TCP2特定的数据包套接字(SOCK_DGRAM)。原始套接字(SOCK_RAW),具有对UDP3 的独特支持。它支持多协议并通过传输层2进行传输。 IP地址

IP 地址是Internet 主机的标识符,对于IPv4 为32 位,对于IPv6 为128 位。路由器使用此信息来选择数据包的路由。 3. 端口号

16 位数字,1-65535。为了区分任务进程转发到的主机接收到的数据包进行处理,端口号用于区分保留端口1 到1023(FTP:24、SSH:22、HTTP80)。可用端口:500065535 TCP 端口号、UDP 端口号独立网络上的通信由IP 地址+端口号决定。 4. 字节顺序

字节顺序意味着当不同的CPU访问内存中的多字节数据时,会出现大端与小端的问题。一般来说,小端模式功耗是x86/ARM 上的一个问题。 /miop:作为arm路由器使用时,以big-endian模式进行网络传输时,使用big-endian模式字节转换功能。

特定系统上使用的字节顺序称为主机字节顺序。引入网络字节排序是为了避免在不同类型的主机之间交换数据时由于顺序差异而导致的错误。从主机字节顺序到网络字节顺序

u_long htonl(u_long hostlong);u_short htons(u_short hostshort); 网络字节顺序到主机字节顺序

u_long ntohl(u_long hostlong);u_short ntohs(u_short hostshort); 数据12被认为是高端数据,78被认为是低端数据。

存储该数据的地址是从0x00开始的,如果地址0x00处存储的是12,则称为big endian。换句话说,上位数据存储在下位存储器中。如果78存储在0x00中,即较低的数据存储在较低的地址中,则称为little endian。

IP地址转换

inet_aton()

将strptr 指向的字符串转换为32 位网络字节顺序二进制值inet_addr()。

功能与上面相同。仅对于IPv4,返回转换后的地址。 限制:255.255.255.255 不能用于转换。

将32 位网络字节顺序的二进制地址转换为点分十进制字符串inet_pton()/inet_ntop()。

int inet_pton(int af, const char* src, void* dst) 将IPv4/IPv6 地址转换为IPv4/IPv6 可使用的二进制格式,并且可以正确处理255.255.255.255 转换问题。

1. 地址协议组(AF_INET 或AF_INET6) 2. src:指针dst:转换结果传递给dst

2.TCP编程

1.socket函数

int套接字(int域,int类型,int协议);

头文件:sys/types.h、sys/socket.h

参数:

整数域

AF_INET: IPv4AF_INET6: IPv6AF_UNIX,AF_LOCAL: 本地类型

SOCK_STREAM:流式套接字唯一对应于TCPSOCK_DGRAM。数据包套接字唯一对应于UDPSOCK_RAM。原始套接字协议

通常为0,必须补充原始套接字编程。

返回值:成功时返回套接字文件描述符,失败时返回-1。

2.bind

int 绑定( int sockfd,

const struct sockaddr *my_addr,

int 地址);

参数:

使用sockfd:socket()函数获得的fdaddr: struct socket结构体地址

struct sockaddr{ //总体结构

unsigned Short sa_family;//2字节

char sa_data[14]; //14字节协议地址

};struct sockaddr_in{ //基于socket通信结构体

sa_family_t sin_family;//2字节

in_port_t sin_port; //2字节

struct in_addr sin_addr;//4字节

sin_zero[8]; //8位,填充字节需要清零

}; 结构in_addr{

uint32_t s_addr; //32位网络字节顺序

addrlen:地址长度

返回值:成功时返回0,失败时返回-1。

3.listen

int Listen(int sockfd,int backlog);

范围:

sockfd:通过socket()函数获得的fd;backlof:允许多个客户端和服务器同时连接(3次握手)。一般输入5(即允许(2*5+1)个客户端同时终止连接)

服务器的socket fd在内核中维护着两个链表

1. 3次握手时的客户端链表2. 建立连接的客户端链表返回:0表示成功,-1表示失败listen(fd,5) //系统监听11(2*5+)是允许的。 1)客户端执行3次握手

4.accept

阻塞等待客户端连接请求

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

范围

sockfd: fdaddr由前面的socket()创建并通过bind()、listen()设置。指向存储地址信息的结构体的首地址。

获取客户端的IP地址和端口号。 addrlen:存储地址信息的结构体的大小。 返回:如果成功,则返回建立连接的新newfd。错误-1。

3.并发服务器

创建并行服务器时,首先了解client.c的创建。

#include stdio.h

#include unistd.h

#包括stdlib.h

#include 字符串.h

#include sys/types.h

#include sys/socket.h

#include arpa/inet.h

//实现./client 192.168.60.128 5001 调用方式。

#define QUIT_STR \’退出\’

#defineBUFSIZE 1024

int main(int argc, char *argv[])

{

int fd=-1;

如果(argc!=3){

结束(1);

}

输入端口=-1;

port=atoi(argv[2]);//标准输入是字符串,所以需要将ascii转换为int

struct sockaddr_in sin;//创建基于socket的通信结构

fd=套接字(AF_INET,SOCK_STREAM,0);

如果(fd0){

pererror(\’套接字\’);

结束(1);

}

bzero(sin,sizeof(sin));

sin.sin_family=AF_INET;

sin.sin_port=htons(port);//从主机字节序转为网络字节序

sin.sin_addr.s_addr=inet_addr(argv[1]);

if(connect(fd,(struct sockaddr *)sin,sizeof(sin))){

错误(\’连接\’);

结束(1);

}

字符buf[BUFSIZE];

同时(1)

{

bzero(buf,BUFSIZE);

if(fgets(buf,BUFSIZE-1,stdin)==NULL){

继续;

}

写(fd,buf,strlen(buf));

if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))

{//strncasecmp 和strcmp 的区别在于strncasecmp 允许指定比较的长度并且不区分大小写。

休息;

}

}

返回0。

}

1.多线程服务器

#include stdio.h

#包括stdlib.h

#include 字符串.h

#include unistd.h

#include sys/types.h

#include sys/socket.h

#包括字符串.h

#include arpa/inet.h

#include pthread.h

#define QUIT_STR \’退出\’

#defineBUFSIZE 1024

#定义待办事项5

#定义SERV_PORT 5001

#define SERV_IP_ADDR \’192.168.60.128\’

无效* client_data_handle(无效* arg);

int main()

{

int fd=-1;

整数a=0;

结构体sockaddr_in sin;

//1.套接字

fd=套接字(AF_INET,SOCK_STREAM,0);

如果(fd 0){

pererror(\’套接字\’);

结束(1);

}

bzero(sin,sizeof(sin));

sin.sin_family=AF_INET;

sin.sin_port=htons(SERV_PORT);//主机到网络的短接

sin.sin_addr.s_addr=INADDR_ANY;//通过该宏自动获取主机IP地址

//2. 绑定

a=bind(fd,(struct sockaddr *)sin,sizeof(sin));

如果(一个0){

错误(\’绑定\’);

结束(1);

}

//3.听

if(listen(fd, BACKLOG)0){//表示系统允许BACKLOG(2*BACKLOG+1)个客户端进行3次握手

pererror(\’听\’);

结束(1);

}

//4.接受

pthread_tid;

int newfd=-1;

结构体sockaddr_in cin;

socklen_t addrlen=sizeof(cin);

而(1){

newfd=接受(fd,(strcut sockaddr *)cin,addrlen);

if(newfd 0){

错误(\’接受\’);

结束(1);

}

字符ipv4_addr[16];

if(inet_ntop(AF_INET,(void *)cin.sin_addr,ipv4_addr,sizeof(cin)))

{

pererror(\’inet_ntop\’);

结束(1);

}

printf(\’clinet:(%s,%d) 已连接!\\n\’,ipv4_addr,ntohs(cin.sin_port));

pthread_create(tid,NULL,client_data_handle,(void *)newfd);

}

关闭(fd);

返回0。

}

无效* client_data_handle(无效* arg)

{

int newfd=*(int *)arg;

字符buf[BUFSIZE];

int ret=-1;

printf(\’句柄pthread: newfd=%d\\n\’,newfd);

同时(1)

{

//读

做{

bzero(buf,BUFSIZE);

ret=读取(newfd,buf,BUFSIZE-1);

同时(ret 1);

if(ret 0)//错误

{

结束(1);

}

如果(!ret){

休息;

}

printf(\’正在从socketfd %d:%s接收数据\\n\’,newfd,buf);

if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))

{

printf(\’客户端正在退出!\\n\’);

休息;

}

}

关闭(newfd);

pthread_join(pthread_self(),NULL);

返回NULL。

}

2.多进程服务器

TCP并发服务器的思想是,每个客户端请求不是由服务器直接处理,而是服务器创建一个子进程来处理。多线程服务器是多进程服务器的改进。

#include stdio.h

#包括stdlib.h

#include 字符串.h

#include unistd.h

#include sys/types.h

#include sys/socket.h

#包括字符串.h

#include arpa/inet.h

#include pthread.h

#包括信号.h

#include sys/wait.h

#define QUIT_STR \’退出\’

#defineBUFSIZE 1024

#定义待办事项5

#定义SERV_PORT 5001

#define SERV_IP_ADDR \’192.168.60.128\’

无效* client_data_handle(无效* arg);

无效child_data_handle(intsignum)

{

if (SIGCHLD==签名)

{

waitpid(-1,NULL,WNOHANG);

}

}

int main()

{

int fd=-1;

信号(SIGCHLD,child_data_handle);

整数a=0;

结构体sockaddr_in sin;

//1.套接字

fd=套接字(AF_INET,SOCK_STREAM,0);

如果(fd 0){

pererror(\’套接字\’);

结束(1);

}

bzero(sin,sizeof(sin));

sin.sin_family=AF_INET;

sin.sin_port=htons(SERV_PORT);

sin.sin_addr.s_addr=INADDR_ANY;

a=bind(fd,(struct sockaddr *)sin,sizeof(sin));

如果(一个0){

错误(\’绑定\’);

结束(1);

}

if(听(fd,BACKLOG)0)

{

pererror(\’听\’);

结束(1);

}

pid_t 进程号;

int newfd=-1;

结构体sockaddr_in cin;

socklen_t addrlen=sizeof(cin);

同时(1)

{

newfd=接受(fd,(struct sockaddr *)cin,addrlen);

if(newfd 0){

错误(\’接受\’);

休息;

}

pid=fork();

如果(pid0){

错误(\’叉子\’);

休息;

}

如果(pid==0)

{

字符ipv4_addr[16];

if(!inet_ntop(AF_INET,(void *)cin.sin_addr,sizeof(cin)))

{

pererror(\’inet_ntop\’);

结束(1);

}

printf(\’client:(%s,%d)已连接!\\n\’,ipv4_addr,ntohs(cin.sin_port));

client_data_handle(newfd);

关闭(fd);

}

如果(PID 0)

{

关闭(newfd);

}

}

关闭(fd);

返回0。

}

无效* client_data_handle(无效* arg)

{

int newfd=*(int *)arg;

字符buf[BUFSIZE];

int ret=-1;

printf(\’子句柄process: newfd=%d\\n\’,newfd);

同时(1)

{

做{

bzero(buf,BUFSIZE);

ret=读取(newfd,buf,BUFSIZE-1);

同时(ret 1);

如果(ret 0)

结束(1);

如果(!ret)

休息;

printf(\’正在从socketfd %d:%s接收数据\\n\’,newfd,buf);

if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))

{

printf(\’客户端正在退出!\\n\’);

break;
}
}
close(newfd);
return NULL;
}

4.TCP的API补充

1.send()函数

ssize_t send(int sockfd,
const void *buf,
size_t len,
int flags
);
参数:sockfd:socket函数返回的fd
buffer:发送缓冲区首地址
length:发送的字节
flags:发送方式(通常为0),和write一样
MSG_DONTWAIT,非阻塞
MSG_OOB,用于TCP类型的带外数据 返回值:成功:实际发送的字节数;失败-1并设置errno

2.recv()

网络中接收数据

int recv(SOCKET s, char FAR *buf, int len, int flags);
flag:一般填0,和read作用一样特殊的标志:
MSG_DONTWAITMSG_OOB:读取带外数据MSG_PEEK:流

5.UDP编程

bind:绑定服务器:TCP地址和端口号

receivefrom():阻塞等待客户端数据

sendto():指定服务器的IP地址和端口号,要发送的数据

无连接尽力传输,UDP是不可靠传输,应用于实时的音视频传输,DNS域名解析包

udp_server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define SERV_PORT 5001
#define BUFSIZE 1024
#define QUIT_STR \”QUIT\”
int main()
{
int fd = -1;
struct sockaddr_in sin;

fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd < 0){
perror(\”socket\”);
exit(1);
}

int b_reuser = 1;
setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&b_reuser,sizeof(int));

bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);

if(bind(fd,(struct sockaddr *)&sin,sizeof(sin)) < 0)
{
perror(\”bind\”);
exit(1);
}
char buf[BUFSIZE];
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
while(1)
{
bzero(buf,BUFSIZE);
if(recvfrom(fd,buf,BUFSIZE-1,0,(struct sockaddr *)&cin,&addrlen) < 0)
{
perror(\”recvfrom\”);
continue;
}
char ipv4_addr[16];
if(!inet_ntop(AF_INET,(void *)&cin.sin_addr,ipv4_addr,sizeof(cin)))
{
perror(\”inet_ntop\”);
exit(1);
}
printf(\”receive from(%s,%d),data:%s\\n\”,ipv4_addr,ntohs(sin.sin_port),buf);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR))){
printf(\”client(%s:%d)is exited!]n\”,ipv4_addr,ntohs(sin.sin_port));
}
}
close(fd);
return 0;
}

udp_client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define BUFSIZE 1024
#define QUIT_STR \”QUIT\”
int main(int argc,char *argv[])
{
if(argc != 3)
{
exit(1);
}
int fd = -1;
fd = socket(AF_INET,SOCK_DGRAM,0);
if(fd < 0){
perror(\”socket\”);
exit(1);
}
int port = -1;
port = atoi(argv[2]);

struct sockaddr_in sin;
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(argv[1]);

char buf[BUFSIZE];
while(1)
{
if(fget(buf,BUFSIZE-1,stdin) == NULL)
{
perror(\”fgets\”);
continue;
}
sendto(fd,buf,strlen(buf),0,(struct sockaddr *)&sin,sizeof(sin));
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))
{
printf(\”client is exited\\n\”);
break;
}
}
close(fd);
return 0;
}

6.IO多路复用

1.IO模型

在unix/linux下主要有四种I/O模式:

阻塞I/O(最常用):
大部分程序使用的都是阻塞模式的I/O缺省情况下,套接字建立后所处于的模式就是阻塞I/O模式读操作:read,recv,recvfrom写操作:write,send其他操作:accept,connect
以read函数为例
进程调用read从套接字上读取数据,如果套接字的接收缓冲区没有数据可读,read函数将发送阻塞。他会一直阻塞下去,等待套接字的接收缓冲区有数据可读接收缓冲区接收到数据后,于是内核便去唤醒该进程,通过read访问这些数据如果在进程阻塞过程中对方发生故障,那这个进程将永远阻塞下去UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议不存在发送缓冲区满的情况,在UDP套接字上执行的写操作永远都不会阻塞。 写阻塞
在写操作时发送阻塞的情况要比读操作少。主要发生在写入的缓冲区小于要写入的数据的情况。 非阻塞I/O:可防止进程阻塞在I/O上,需要轮询
当一个应用程序不停的polling内核来检查是否I/O操作已经就绪,这会极其浪费资源。这种模式使用不普遍
1.fcntl()函数当你一开始建立一个套接字描述符的时候,系统内核将其设置为阻塞IO模式。可以使用fcntl函数设置一个套接字的标志位为O_NOBLOCK来实现阻塞
int fcntl(int fd, int cmd, long arg);
int flag;
flag = fcntl(sockfd,F_GETFL,0);
flag |= O_NOBLOCK;
fcntl(sockfd,F_SETFL,flag);

I/O多路复用:允许同时对多个I/O进行控制
基本常识:linux中每个进程最多可以打开1024个文件,最多有1024个文件描述符,文件描述符的特点:
1.非负整数
2.从最小可用的数字来分配
3.每个进程启动时默认打开0,1,2三个文件描述符
多路复用不止针对套接字fd,也针对普通的文件描述符fd应用程序中同时处理多路输入输出流,若采用阻塞模式,将得不到预期目的。若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得复杂;比较好的方法是使用IO多路复用,基本思想是:
先构造一张有关描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已准备好进行IO时函数才返回。函数返回时告诉进程哪个描述符已就绪,可以进行IO操作。
步骤:

1.fd_set

void FD_ZERO(fd_set* fdset); //对集合清零void FD_SET(int fd, fd_set* fdset); //把fd加入集合void FD_CLR(int fd,fd_set *fdset); //从集合中清除fdvoid FD_ISSET(int fd,fd_set *fdset); //判断fd是否在fd_set中
2.select()

​ int select(int maxfdp, //maxfd + 1
​ fd_set *readfds, //读集合
​ fd_set *writefds, //写集合
​ fd_set *errorfds, //异常集合
​ struct timeval *timout //超时
​ );

一般填 写集合,写集合填空,异常集合

2.select模型:

int main(void)
{
struct timeval tout;
fd_set rset;
int maxfd = -1;
fd = socket(…);
bind(fd,…);
listen(fd,…);
while(1){
maxfd = fd;
FD_ZERO(&rset);
FD_SET(&rset);
tout.tv_sec = 5;
tout.tv_usec = 0;
select(maxfd+1,&rset,NULL,NULL,&tout);//阻塞
if(FD_ISSET(fd,&rset)) //有1或多个fd有数据
{
newfd = accept(fd,…);
//依次判断已建立连接的客户端是否有数据
//xxx
}
}
}

3.select_client.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/time.h>
#define BUFSIZE 1024
#define SERV_RESP_STR \”SERVER:\”
int main(int argc, char *argv[])
{
int fd = -1;
if(argc!=3)
exit(1);
int port = -1;
port = atoi(argv[2]);
struct sockaddr_in sin;
fd = socket(AF_INET,SOCK_STREAM,0);
if(fd < 0){
perror(\”socket\”);
exit(1);
}
bzero(&sin,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(fd,(struct sockaddr *)&sin,sizeof(sin))){
perror(\”connect\”);
exit(1);
}
fd_set rset;
int maxfd = -1;
struct timeval tout;
char buf[BUFSIZE];
int ret = -1;
while(1)
{
FD_ZERO(&rset);
FD_SET(0,&rset); //把stdin加入集合
FD_SET(fd,&rset); //把fd加入集合

maxfd = fd;
tout.tv_sec = 5;
tout.tv_usec = 0;
select(maxfd+1,&rest,NULL,NULL,&tout);//阻塞等待集合有数据

if(FD_ISSET(0,&rset)) //stdin输入有数据
{
bzero(buf,BUFSIZE);
do
{
ret = read(0,buf,BUFSIZE-1);
}while(ret < 0);
if(ret < 0)
{
perror(\”read\”);
continue;
}
if(ret == 0)continue;//no data

if(write(fd,buf,strlen(buf)) < 0){
perror(\”write\”);
continue;
}
if(!strncasecmp(buf,\”quit\”,strlen(\”quit\”))){
printf(\”client has exited\\n\”);
break;
}
}
if(FD_ISSET(fd,&rset)) //fd,即server有数据
{
bzero(buf,BUFSIZE);
do{
ret = read(fd,buf,BUFSIZE-1);
}while(ret < 0);
if(ret < 0)
{
perror(\”read\”);
continue;
}
if(ret==0)break;
printf(\”server said:%s\\n\”,buf);
if((strlen(buf)>strlen(SERV_RESP_STR)) && !strncasecmp(buf,\”quit\”,strlen(\”quit\”))){
printf(\”client has exited\\n\”);
break;
}
}

}
return 0;
}

4.select_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#include <sys/wait.h>
#define QUIT_STR \”QUIT\”
#define BUFSIZE 1024
#define BACKLOG 5
#define SERV_PORT 5001
#define SERV_RESP_STR \”SERVER:\”
void *client_data_handle(void *arg);
void child_data_handle(int signum)
{
}
int main()
{
int fd = -1;

int a = -1;
struct sockaddr_in sin;
//1.socket
fd = socket(AF_INET,SOCK_STREAM,0);
if(fd < 0)
{
perror(\”socket\”);
exit(1);
}
bzero(&sin,sizeof(sin));

sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = INADDR_ANY;
a = bind(fd,(struct sockaddr *)&sin,sizeof(sin));
if(a < 0)
{
perror(\”bind\”);
exit(1);
}
a = listen(fd,BACKLOG);
if(a < 0){
perror(\”listen\”);
exit(1);
}

pid_t pid;
int newfd = -1;
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
while(1)
{
newfd = accept(fd,(struct sockaddr *)&cin,&addrlen);
if(newfd < 0)
{
perror(\”accept\”);
break;
}
pid = fork();
if(pid < 0)
{
perror(\”fork\”);
break;
}
if(pid == 0)
{
char ipv4_addr[16];
if(!inet_ntop(AF_INET,(void *)&cin.sin_addr,ipv4_addr,sizeof(cin))){
perror(\”inet_ntop\”);
exit(1);
}
printf(\”client:(%s,%d)is connect!\\n\”,ipv4_addr,ntohs(cin.sin_port));
client_data_handle(&newfd);
close(fd);
}
if(pid > 0)
{
close(newfd);
}

}
close(fd);
return 0;
}
void *client_data_handle(void *arg)
{
int newfd = *(int *)arg;
char buf[BUFSIZE];
int ret = -1;
printf(\”child handle process: newfd = %d\\n\”,newfd);
char resp_buf[BUFSIZE];
while(1)
{
do{
bzero(buf,BUFSIZE);
ret = read(newfd,buf,BUFSIZE-1);
}while(ret < 1);
if(ret < 0)exit(1);
if(ret == 0)break;
printf(\”receive data from socketfd %d:%s\\n\”,newfd,buf);

bzero(resp_buf,BUFSIZE);
strncpy(resp_buf,SERV_RESP_STR,strlen(SERV_RESP_STR));
strcat(resp_buf,buf);
do{
ret = write(newfd,resp_buf,strlen(resp_buf));
}while(ret < 0);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))
{
printf(\”client is exiting!\\n\”);
break;
}
}
close(newfd);
return NULL;
}

7.TCP三次握手,四次挥手(面试重点问题)

​ 假设有服务器server和客户端client,服务器在socket创建套接字,bind绑定socket通信结构体和listen使服务器进入监听状态后,然后在accept函数阻塞。这时候客户端通过socket创建套接字后使用connect函数,此时三次握手开始:

​ 第一次握手:客户端给服务器发送一个SYN报文;第二次握手:服务器收到SYN报文之后,会应答一个SYN+ACK报文;第三次握手:客户端收到SYN+ACK报文后,会回应一个ACK报文。服务器收到ACK报文之后,三次握手建立完成。

​ 四次挥手:刚开始双方都处于established状态,假如是客户端先发起关闭请求,则:
​ 第一次挥手:客户端发送一个FIN报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。
​ 第二次挥手:服务器收到FIN之后,会发送ACK报文,并且把客户端的序列号值加1作为ACK报文的序列号值,表明已经收到客户端的报文了,此时服务器处于CLOSE_WAIT状态。
​ 第三次挥手:如果服务器也想断开连接了,和客户端的第一次挥手一样,发送FIN报文,且指定一个序列号。此时服务器处于LAST_ACK状态。
​ 第四次挥手:客户端收到FIN之后,再发送一个ACK报文作为应答,且把服务端的序列号加1作为ACK报文的序列号值,此时客户端处于TIME_WAIT状态。需要确保服务器收到ACK报文之后才会进入CLOSED状态。
最后,服务端收到ACK报文之后,就处于CLOSED状态了。

一定要标注客户端和服务器三次握手的连接必须是客户端发起的SYN,ACK,FIN等标志符号应该写上

#以上关于Linux网络编程的相关内容来源网络仅供参考,相关信息请以官方公告为准!

原创文章,作者:CSDN,如若转载,请注明出处:https://www.sudun.com/ask/94017.html

(0)
CSDN's avatarCSDN
上一篇 2024年7月26日 下午8:00
下一篇 2024年7月26日 下午8:00

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注