Linux网络编程
1.网络基础
网络互联促成了TCP/IP协议的产生,早期的ARPAnet使用网络控制协议,不能互联不同类型的计算机和不同类型的操作系统,没有纠错功能。1974年第一份TCP协议详细说明发表。
TCP协议分成了两个不同的协议:
- 用来检测网络传输中差错的的传输控制协议TCP
- 专门负责对不同网络进行互联的互联网协议IP
- 从此TCP/IP协议诞生
TCP/IP协议成为了Internet中的“世界语”。
网络的体系结构
- 网络采用分而治之的方法设计,将网络的功能划分为不同模块,以分层的形式有机结合在一起。
- 每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上提供服务,同时使用下层提供的服务。
- 网络体系结构即指网络的层次结构和每层所使用的协议的集合。
- 两类非常重要的体系结构:OSI和TCP/IP。
OSI模型共有七层:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。OSI模型是一个理想化的模型,尚未有完整的实现。OSI模型相关协议已经很少使用,但模型本身非常通用。
TCP/IP协议族体系:TCP/IP是Internet上的工业标准,一共有四层:
应用层 | Relnet,FTP,HTTP,DNS,SMTP等 |
---|---|
传输层 | TCP和UDP |
网络层 | IP,ICM和IGMP,端到端传输 |
网络接口和物理层 | 以太网,令牌环网,FDDI,wifi,gps/2G/3G/4G |
网路预备知识
-
1.socket
- 是一个编程接口,是一个特殊的文件描述符(对它执行IO的操作函数,比如read,write,close等),并不限于TCP/IP协议,面向连接TCP,无连接UDP。
- socket代表网络编程的一种资源
- 分类:
- 1.流式套接字(SOCK_STREAM)。唯一对应TCP
- 2.数据包套接字(SOCK_DGRAM)。唯一对应UDP
- 3.原始套接字(SOCK_RAW)。对应多个协议,发送穿透了传输层
-
2.IP地址
- IP地址是Internet主机的标识符,Ipv4为32位,Ipv6为128位,每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由。
-
3.端口号
- 16位数字,1-65535
- 为了区分一台主机接收到的数据包应该转交给哪个任务进程处理,使用端口号来区别
- 预留端口1-1023(FTP:24,SSH:22,HTTP80)
- 可以使用的端口:5000~65535
- TCP端口号与UDP端口号独立
- 网络里的通信是由 IP地址+端口号 来决定的。
-
4.字节序
- 字节序 是指不同的CPU访问内存中的多字节数据时,存在大小端的问题
- 如果CPU访问的是字符串则不存在大小端问题
- 一般来说x86/ARM:小端模式
- power/miop:arm作为路由时,大端模式
- 网络传输的时候采用大端模式
- 字节转换函数
- 把给定系统所采用的字节序称为主机字节序,为了避免不同类型主机之间在数据交换时由于对于序的不同而导致的差错,引入了网络字节序。
- 主机字节序到网络字节序
- u_long htonl(u_long hostlong);
- u_short htons(u_short hostshort);
- 网络字节序到主机字节序
- u_long ntohl(u_long hostlong);
- u_short ntohs(u_short hostshort);
有一个数据0x12345678。12被认为是高端数据,78是低端数据。 对于存储这一个数据的地址,假设从0x00开始存储,如果将12存储在0x00这个地址上,那么就称为大端:低端内存存放着高端数据。如果0x00存储着78,即低地址存放着低端数据,则称为小端。
- IP地址的转换
- inet_aton()
- 将strptr所指的字符串转换成32位的网络字节序二进制值
- inet_addr()
- 功能同上,返回转换后的地址
- 仅适用于IPv4,出错返回-1
- 局限性:不能使用255.255.255.255转换
- inet_ntoa()
- 将32位网络字节序二进制地址转换成点分十进制的字符串
- inet_pton()/inet_ntop()
- int inet_pton(int af,const char* src, void* dst)
- 将IPv4/IPv6的地址转换成binary格式
- 使用于IPv4/IPv6
- 能正确处理255.255.255.255的转换问题
- 参数:
- 1.地址协议组(AF_INET或AF_INET6)
- 2.src:是一个指针
- dst:转换的结果给到dst
- inet_aton()
2.TCP编程
1.socket函数
int socket(int domain, int type, int protocol);
头文件:<sys/types.h>、<sys/socket.h>
参数:
- int demain
- AF_INET: IPv4
- AF_INET6: IPv6
- AF_UNIX,AF_LOCAL: Local
- type
- SOCK_STREAM:流式套接字 唯一对应TCP
- SOCK_DGRAM:数据包套接字 唯一对应UDP
- SOCK_RAM:原始套接字
- protocol
- 一般为0,原始套接字编程则需要补充
返回值:成功返回socket文件描述符,失败返回-1
2.bind
int bind( int sockfd,
const struct sockaddr *my_addr,
int addrlen);
参数:
- sockfd:通过socket()函数得到的fd
- addr:采用 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位,填充字节,需清零
}; - struct in_addr{
uint32_t s_addr; //32位网络字节序
};
- struct sockaddr{ //通用结构体
- addrlen:地址长度
返回值:成功返回0,失败-1
3.listen
int listen(int sockfd,int backlog);
- 参数:
- sockfd:通过socket()函数拿到的fd;
- backlof:同时允许几路客户端和服务器进行正在连接的过程(正在三次握手),一般填5(也就是同时允许(2*5+1)个客户端连接)
- 内核中服务器的套接字fd会维护2个链表
- 1.正在三次握手的客户端链表
- 2.已经建立好连接的客户端链表
- 内核中服务器的套接字fd会维护2个链表
- 返回值:成功0,失败-1
- listen(fd,5); //表示系统允许11(2*5+1)个客户端进行三次握手
4.accept
阻塞等待客户端连接请求
int accpet(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- 参数
- sockfd:经过前面socket()创建并通过bind(),listen()设置过的fd
- addr:指向存放地址信息的结构体的首地址
- 获取客户端IP地址和端口号
- addrlen:存放地址信息的结构体的大小
- 返回值:成功,返回已经建立连接的新的newfd。出错-1。
3.并发服务器
在编写并发服务器时,先了解一下客户端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>
// 实现./client 192.168.60.128 5001的调用形式
#define QUIT_STR "QUIT"
#define BUFSIZE 1024
int main(int argc, char *argv[])
{
int fd = -1;
if(argc!=3){
exit(1);
}
int port = -1;
port = atoi(argv[2]);//因为我们stdin输入的是字符串,所以要ascii to int
struct sockaddr_in sin;//创建基于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(port);//主机字节序到网络字节序
sin.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(fd,(struct sockaddr *)&sin,sizeof(sin))){
perror("connect");
exit(1);
}
char buf[BUFSIZE];
while(1)
{
bzero(buf,BUFSIZE);
if(fgets(buf,BUFSIZE-1,stdin)==NULL){
continue;
}
write(fd,buf,strlen(buf));
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))
{ //strncasecmp与strcmp的区别在于:strncasecmp能指定比较的长度,并且不区分大小写
break;
}
}
return 0;
}
1.多线程服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <pthread.h>
#define QUIT_STR "QUIT"
#define BUFSIZE 1024
#define BACKLOG 5
#define SERV_PORT 5001
#define SERV_IP_ADDR "192.168.60.128"
void * client_data_handle(void *arg);
int main()
{
int fd = -1;
int a=0;
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);//host to network short
sin.sin_addr.s_addr = INADDR_ANY; //通过这个宏自动获取主机ip地址
//2.bind
a = bind(fd,(struct sockaddr *)&sin,sizeof(sin));
if(a < 0){
perror("bind");
exit(1);
}
//3.listen
if(listen(fd, BACKLOG)<0){//表示系统允许BACKLOG(2*BACKLOG+1)个客户端进行三次握手
perror("listen");
exit(1);
}
//4.accept
pthread_ tid;
int newfd = -1;
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
while(1){
newfd = accept(fd,(strcut sockaddr *)&cin,&addrlen);
if(newfd < 0){
perror("accpet");
exit(1);
}
char ipv4_addr[16];
if(inet_ntop(AF_INET,(void *)&cin.sin_addr,ipv4_addr,sizeof(cin)))
{
perror("inet_ntop");
exit(1);
}
printf("clinet:(%s,%d)is connect!\n",ipv4_addr,ntohs(cin.sin_port));
pthread_create(&tid,NULL,client_data_handle,(void *)&newfd);
}
close(fd);
return 0;
}
void *client_data_handle(void *arg)
{
int newfd = *(int *)arg;
char buf[BUFSIZE];
int ret = -1;
printf("handle pthread: newfd = %d\n",newfd);
while(1)
{
//read
do{
bzero(buf,BUFSIZE);
ret = read(newfd,buf,BUFSIZE-1);
}while(ret < 1);
if(ret < 0)//error
{
exit(1);
}
if(!ret){
break;
}
printf("receive data from socketfd %d:%s\n",newfd,buf);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))
{
printf("client is exiting!\n");
break;
}
}
close(newfd);
pthread_join(pthread_self(),NULL);
return NULL;
}
2.多进程服务器
TCP并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是由服务器创建一个子进程来处理。多线程服务器是对多进程的服务器的改进。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#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_IP_ADDR "192.168.60.128"
void* client_data_handle(void* arg);
void child_data_handle(int signum)
{
if (SIGCHLD == signum)
{
waitpid(-1,NULL,WNOHANG);
}
}
int main()
{
int fd = -1;
signal(SIGCHLD,child_data_handle);
int a=0;
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);
}
if(listen(fd,BACKLOG)<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("accecpt");
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,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);
while(1)
{
do{
bzero(buf,BUFSIZE);
ret = read(newfd,buf,BUFSIZE-1);
}while(ret < 1);
if(ret < 0)
exit(1);
if(!ret)
break;
printf("receive data from socketfd %d:%s\n",newfd,buf);
if(!strncasecmp(buf,QUIT_STR,strlen(QUIT_STR)))
{
printf("client is exiting!\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_DONTWAIT
- MSG_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套接字上执行的写操作永远都不会阻塞。
- 写阻塞
- 在写操作时发送阻塞的情况要比读操作少。主要发生在写入的缓冲区小于要写入的数据的情况。
- 以read函数为例
- 非阻塞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操作。
- 基本常识:linux中每个进程最多可以打开1024个文件,最多有1024个文件描述符,文件描述符的特点:
步骤:
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); //从集合中清除fd
- void 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等标志符号应该写上