转载请声明博主https://mp.csdn.net/console/editor/html/106517098
一、Linux网络概述
①LINUX网络优势
②Linux网络模型
③网络层协议
第一部分为网络层协议。主要包括Internet协议(IP)、网际控制报文(ICMP)和地址解析协议(ARP)
.Internet协议(IP)
该协议被设计成互联分组交换通信网,以形成一个网际通信环境。它负责在源主机和目的地主机之间传输来自其较高层软件的称为数据报文的数据块,它在源和目的地之间提供非链接型传递服务
*网际控制报文协议(ICMP)
它实际上不是IP层部分,但直接同IP层一起工作,报告网络上的某些出错情况。如 ping命令
地址解析协议(ARP)
ARP实际上不是网络层部分,它处于IP和数据链路层之间,它是在32位IP地址和48位物理地址之间执行翻译的协议
④传输层协议
第二部分是传输层协议,包括传输控制协议(tcp)和用户数据报文协议(udp)
*传输控制协议(TCP):
提供可靠的有连接的传输层服务。方式如同打电话,必须先接通才能说话。
*用户数据报文协议(UDP):
UDP提供不可靠的非连接型传输层服务。方式如同发短信,任性的发不用管对方看不看
⑤应用层协议
主要包括Telnet,文件传送协议(FTP和TFTP),简单文件传送协议(SMTP),域名服务(DNS),超文本传输协议(http)等协议
⑥协议封装
⑦以太网包
⑧IP协议
IP主要有以下四个主要功能:
*数据传送
*寻址
*路由选择
*数据报文的分段
IP的主要目的是为数据输入/输出网路提供基本算法,为高层协议提供无连接的传送服务。
IP包由IP协议头和协议数据两部分构成。
IP协议头结合wireshark分析
⑨IP协议数据报格式
⑩TCP协议头
TCP协议头(wireshark分析)
11、UDP的协议头
二、Linux网络编程基础
①socket
Linux中的网络编程通过Socket(套接字)实现,Socket是一种文件描述符。
Linux中一切设备都是文件。
②Socket有三种类型:
*流式套接字(SOCK_STREAM) 用于TCP
*数据报套接字(SOCK_DGRAM) 用于UDP
*原始套接字(SOCK_RAW) 用于ICMP如ping
③网络地址
在socket程序设计中,struct sockaddr用于记录网络地址:
struct sockaddr
{
u_short sa_family;
char sa_data[14];
}
# sa_family:
协议族,采用”AF_XXX”的形式,如:AF_INET(IP协议族)。
#sa_data:
14字节的特定协议地址
在socket程序设计中,struct sockaddr_in 同样用于记录网络地址
Struct sockaddr_in
{
short int sin_family;/*协议族*/
unsigned short int sin_port;/*端口号*/
struct in_addr sin_addr; /*协议特定地址*/
unsigned char sin_zero[8]; /*预留填充0*/
}
编程中一般使用与sockaddr等价的sockaddr_in数据结构
④地址结构
⑤地址转换
IP地址通常由数字加点(192.168.0.1)的形式表示,而在struct in_addr中使用的IP地址是由32位的整数表示,为了转换我们可以使用下面两个函数:
#int inet_aton(const char*cp,struct in_addr *inp)
#char *inet_ntoa(struct in_addr in)
函数里面a代表ascii,n代表network.
Inet_aton是将a.b.c.d形式的IP转换为32位的IP,存储在inp指针里面。Inet_ntoa是将32位IP转换为a.b.c.d的格式
⑥字节序转换
不同类型的CPU对变量的字节存储顺序可能不同:有的系统是高位在前,低位在后,而有的系统是低位在前,高位在后,而网络传输的数据顺序是一定要是统一的。所以当内部字节存储顺序和网络字节序(big endian)不同时,就一定要进行转换
为什么要进行字节序转换?
例如:
大端架构的CPU和小端架构的CPU就会出问题。大端发送一个16位数据0x1234给小端,这是小端就会认为受到了0x3412.
所以需要字节序转换,尤其是如果在网络上都遵循大端发送,那么在接收的CPU上进行字节序转换,最方便了。
#htons
把unsigned short类型从主机序转换到网络序
#htonl
把unsigned long类型从主机序转换到网络序
#ntohs
把unsigned short类型从网络序转换到主机序
#ntohl
把unsigned long类型从网络序转换到主机序
⑦IP与主机名
在网络中标识一台主机可以用IP地址,也可以使用主机名
struct hostent *gethostbyname(const char *hostname)
struct hostent
{
char *h_name; /*主机的正式名称*/
char *h_aliases;/*主机的别名*/
int h_addrtype; /*主机的地址类型 AF_INET*/
int h_length; /*主机的地址长度*/
char **h_addr_list; /*主机的IP地址列表*/
}
#define h_addr h_addr_list[0] /*主机的第一IP地址*/
⑧socket的编程函数
进行Socket编程的常用函数有:
*socket
创建一个socket。
*bind
用于绑定IP地址和端口号到socket
*connect
该函数用于与服务器建立连接
#listen
设置服务器能处理的最大连接请求数
#accept
用来等待来自客户端的socket连接请求
#send
发送数据
#recv
接收数据
三、TCP网络程序设计
①基于TCP-服务器
1.创建一个socket,用函数socket()
2.绑定IP地址、端口等信息到socket上,用函数bind()
3.设置允许的最大连接数,用函数listen()
4.等待来自客户端的连接请求,用函数accept()
5.收发数据,用函数send()和recv(),或者read()和write()
6.关闭网络连接
②基于TCP-客户端
1.创建一个socket,用函数socket()
2.设置要连接的服务器的IP地址和端口等属性
3.连接服务器,用函数connect()
4.收发数据,用函数send和recv,或者read和write
5.关闭网络连接
③基于TCP-通讯模型
④TCP实例分析与演示
tcp_client.c
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define BUFSIZE 512
#define MY_PORT 3333
/*堵塞读*/
void read_data_from_common_sockfd(int sockfd)
{
char text[BUFSIZE] = {'\0'};
FILE * pf;
pf = fopen("1.txt","a+");
while(recv(sockfd,text,BUFSIZE,0)>0){
printf("client recv:%s\n",text);
fputs(text,pf);
memset(text,'\0',BUFSIZE);
}
fclose(pf);
}
/*非阻塞读*/
void read_data_from_select_sockfd(int sockfd)
{
fd_set fdset;
char read_buf[512] = {'\0'};
while(1){
int flag = 0;
int ret = 0;
struct timeval timeout = {2,0};
//timeout.tv_sec = 2;
//timeout.tv_usec = 0;
FD_ZERO(&fdset);
FD_SET(sockfd, &fdset);
ret = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
if(ret <= 0){
continue;
}else{
if(FD_ISSET(sockfd, &fdset)){
int tmp_read = 0, all_read = 0;
while((tmp_read = read(sockfd, read_buf + all_read, sizeof(read_buf))) != 0)
all_read += tmp_read;
printf("select client recv:%s\n", read_buf);
break;
}
}
}
}
int main(int argc,char *argv[])
{
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr;
struct hostent *host;
if(argc != 2){
fprintf(stderr,"Usage:%s hostname \a\n",argv[0]);
exit(1);
}
if((host = gethostbyname(argv[1])) == NULL){
fprintf(stderr,"Gethostbyname error\n");
exit(1);
}
/*客户程序开始建立 sockfd描述符*/
if((sockfd=socket(AF_INET,SOCK_STREAM,0)) == -1){ //AF_INET:IPV4 internet; SOCK_STREAM :TCP
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
exit(1);
}
/*客户程序填充服务端的资料*/
bzero(&server_addr,sizeof(server_addr)); //初始化,置0
server_addr.sin_family = AF_INET; //IPV4
server_addr.sin_port = htons(MY_PORT); //将本机器上的short数据转化为网络上的short数据
server_addr.sin_addr = *((struct in_addr *) host->h_addr); //IP地址
/*客户端程序发起连接请求*/
if(connect(sockfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr)) == -1){
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
exit(1);
}
/*连接成功了*/
printf("Plese input char:\n");
/*发送数据*/
fgets(buffer,1024,stdin);
write(sockfd,buffer,strlen(buffer));
//read_data_from_common_sockfd(sockfd);
read_data_from_select_sockfd(sockfd);
/*结束通讯*/
close(sockfd);
}
tcp_server.c
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define MY_PORT 3333
int main(int argc,char *argv[])
{
int sockfd,new_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size;
int nbytes;
char buffer[1024];
/*服务器端开始建立sockfd描述符*/
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ //AF_INET:IPV4,SOCK_STREAM:TCP
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
exit(1);
}
/*服务器端填充sockaddr结构*/
bzero(&server_addr,sizeof(struct sockaddr_in)); //初始化,置0
server_addr.sin_family = AF_INET; //IP
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //将本机器上的long数据转化为网络上的long
//server_addr.sin_addr.s_addr = inet_addr("192.168.1.1"); //用于绑定到一个固定IP,这里使用了inet_addr函数
server_addr.sin_port = htons(MY_PORT); //将本机器上的short数据转化为网络上的short格式
/*捆绑sockfd描述符到IP地址*/
if(bind(sockfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr)) == -1){
fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
exit(1);
}
/*设置允许连接的最大客户数*/
if(listen(sockfd,5) == -1)
{
fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
exit(1);
}
while(1){
/*服务器阻塞,直到客户程序建立连接*/
sin_size = sizeof(struct sockaddr_in);
if((new_fd = accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size)) == -1){
fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
exit(1);
}
fprintf(stderr,"Server get connection from %s\n",inet_ntoa(client_addr.sin_addr));
if((nbytes = read(new_fd,buffer,1024)) == -1){
fprintf(stderr,"Read Error:%s\n",strerror(errno));
exit(1);
}
buffer[nbytes] = '\0';
printf("Server received %s\n",buffer);
write(new_fd,buffer,strlen(buffer));
/*这个通讯已经结束*/
close(new_fd);
/*循环下一个*/
}
/*结束通讯*/
}
四、UDP网络程序设计
①基于UDP-服务器
1.创建一个socket,用函数socket()
2.绑定IP地址,端口信息到socket上,用函数bind()
3.循环接收数据,用函数recvfrom()
4.关闭网络连接
②基于UDP-客户端
1.创建一个socket,用函数socket()
2.绑定IP地址,端口等信息到socket上,用函数bind()
3.设置对方的IP地址和端口等属性
4.发送数据,用函数sendto()
5.关闭网络连接
③基于UDP的通信模型
使用tcpdump抓包tcpdump -i lo -s 0 -w SuccessC2Server.pcap然后用windows端的wireshark打开SuccessC2Server.pcap 文件分析
udp_server.c
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define SERVER_PORT 8888
#define MAX_MSG_SIZE 1024
void udps_respon(int sockfd)
{
struct sockaddr_in addr;
int addrlen,n;
char msg[MAX_MSG_SIZE];
while(1){
/*从网络上读,并写到网络上*/
bzero(msg,sizeof(msg)); //初始化,置0
addrlen = sizeof(struct sockaddr);
n = recvfrom(sockfd,msg,MAX_MSG_SIZE,0,(struct sockaddr*)&addr,&addrlen); //从客户端接收数据
msg[n] = '\0';
/*显示服务端已经收到了消息*/
fprintf(stdout,"Server have received %s",msg); //显示消息
}
}
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in addr;
/*建立sockfd描述符*/
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0){
fprintf(stderr,"Socket Error:%s\n",strerror(errno));
exit(1);
}
/*服务器端填充sockaddr结构*/
bzero(&addr,sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr=htonl(INADDR_ANY);
addr.sin_port = htons(SERVER_PORT);
/*捆绑sockfd描述符*/
if(bind(sockfd,(struct sockaddr*)&addr,sizeof(struct sockaddr_in)) < 0){
fprintf(stderr,"Bind Error:%s\n",strerror(errno));
exit(1);
}
printf("udp server start ......\n");
udps_respon(sockfd); //进行读写操作
close(sockfd);
}
udp_client.c
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define SERVER_PORT 8888
#define MAX_BUF_SIZE 1024
void udpc_requ(int sockfd,const struct sockaddr_in *addr,int len)
{
char buffer[MAX_BUF_SIZE];
int n;
while(1){
/*从键盘读入,写到服务器*/
printf("Please input char:\n");
fgets(buffer,MAX_BUF_SIZE,stdin);
sendto(sockfd,buffer,strlen(buffer),0,(struct sockaddr*)addr,len);
bzero(buffer,MAX_BUF_SIZE);
}
}
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in addr;
if(argc != 2){
fprintf(stderr,"Usage:%s server_ip\n",argv[0]);
exit(1);
}
/*建立sockfd描述符*/
sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0){
fprintf(stderr,"Socket Error:%s\n",strerror(errno));
exit(1);
}
/*填充服务端的资料*/
bzero(&addr,sizeof(struct sockaddr_in)); //初始化,置0
addr.sin_family = AF_INET;
addr.sin_port = htons(SERVER_PORT);
if(inet_aton(argv[1],&addr.sin_addr) < 0){ /*inet_aton函数用于把字符串型的IP地址转化成网络类型IP地址*/
fprintf(stderr,"Ip Error:%s\n",strerror(errno));
exit(1);
}
udpc_requ(sockfd,&addr,sizeof(struct sockaddr_in)); //进行读写操作
close(sockfd);
}
五、Linux并发服务器设计
①服务器模型
在网络程序里面,一般来说都是许多客户对应一个服务器,为了处理客户的请求,对服务器的程序就提出了特殊的请求。目前最常用的服务器模型有:
. 循环服务器: 服务器在同一个时刻只能想要一个客户端的请求
. 并发服务器:服务器在同一时刻可以响应多个客户端的请求
②UDP循环服务器
UDP的循环服务器的实现方法:
UDP服务器每次从套接字上读取一个客户端的请求->处理->然后将结构返回给客户机。
socket(…);
bind(…);
while(1){
recvfrom();
process();//处理
sendto();
}
③TCP循环服务器
TCP循环服务器接受一个客户端的连接,然后处理,完成了这个客户的所有请求后,断开连接:
socket(…);
bind(…);
listen(…);
while(1){
accept(…);
process(…);
close(…);
}
TCP循环服务器一次只能处理一个客户端的请求。只有在这个客户的所有请求都满足后,服务器才可以继续后面的请求。这样如果有一个客户端占住服务器不放时,其它的客户机都不能工作了,因此,TCP服务器一般很少用循环服务器模型的。
演示:
tcp_server.c
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define MY_PORT 3333
int main(int argc,char *argv[])
{
int sockfd,new_fd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size;
int nbytes;
char buffer[1024];
/*服务器端开始建立sockfd描述符*/
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){ //AF_INET:IPV4,SOCK_STREAM:TCP
fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
exit(1);
}
/*服务器端填充sockaddr结构*/
bzero(&server_addr,sizeof(struct sockaddr_in)); //初始化,置0
server_addr.sin_family = AF_INET; //IP
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //将本机器上的long数据转化为网络上的long
//server_addr.sin_addr.s_addr = inet_addr("192.168.1.1"); //用于绑定到一个固定IP,这里使用了inet_addr函数
server_addr.sin_port = htons(MY_PORT); //将本机器上的short数据转化为网络上的short格式
/*捆绑sockfd描述符到IP地址*/
if(bind(sockfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr)) == -1){
fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
exit(1);
}
/*设置允许连接的最大客户数*/
if(listen(sockfd,5) == -1)
{
fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
exit(1);
}
while(1){
/*服务器阻塞,直到客户程序建立连接*/
sin_size = sizeof(struct sockaddr_in);
if((new_fd = accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size)) == -1){
fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
exit(1);
}
fprintf(stderr,"Server get connection from %s\n",inet_ntoa(client_addr.sin_addr));
if((nbytes = read(new_fd,buffer,1024)) == -1){
fprintf(stderr,"Read Error:%s\n",strerror(errno));
exit(1);
}
buffer[nbytes] = '\0';
printf("Server received %s\n",buffer);
write(new_fd,buffer,strlen(buffer));
/*这个通讯已经结束*/
close(new_fd);
/*循环下一个*/
}
/*结束通讯*/
}
tcp_client.c 多客户端
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define BUFSIZE 512
#define MY_PORT 3333
/*堵塞读*/
void read_data_from_common_sockfd(int sockfd)
{
char text[BUFSIZE] = {'\0'};
FILE * pf;
pf = fopen("1.txt","a+");
while(recv(sockfd,text,BUFSIZE,0)>0){
printf("client recv:%s\n",text);
fputs(text,pf);
memset(text,'\0',BUFSIZE);
}
fclose(pf);
}
/*非阻塞读*/
void read_data_from_select_sockfd(int sockfd)
{
fd_set fdset;
char read_buf[512] = {'\0'};
while(1){
int flag = 0;
int ret = 0;
struct timeval timeout = {2,0};
//timeout.tv_sec = 2;
//timeout.tv_usec = 0;
FD_ZERO(&fdset);
FD_SET(sockfd, &fdset);
ret = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
if(ret <= 0){
continue;
}else{
if(FD_ISSET(sockfd, &fdset)){
int tmp_read = 0, all_read = 0;
while((tmp_read = read(sockfd, read_buf + all_read, sizeof(read_buf))) != 0)
all_read += tmp_read;
printf("select client recv:%s\n", read_buf);
break;
}
}
}
}
int main(int argc,char *argv[])
{
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr;
struct hostent *host;
if(argc != 2){
fprintf(stderr,"Usage:%s hostname \a\n",argv[0]);
exit(1);
}
if((host = gethostbyname(argv[1])) == NULL){
fprintf(stderr,"Gethostbyname error\n");
exit(1);
}
/*客户程序开始建立 sockfd描述符*/
if((sockfd=socket(AF_INET,SOCK_STREAM,0)) == -1){ //AF_INET:IPV4 internet; SOCK_STREAM :TCP
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
exit(1);
}
/*客户程序填充服务端的资料*/
bzero(&server_addr,sizeof(server_addr)); //初始化,置0
server_addr.sin_family = AF_INET; //IPV4
server_addr.sin_port = htons(MY_PORT); //将本机器上的short数据转化为网络上的short数据
server_addr.sin_addr = *((struct in_addr *) host->h_addr); //IP地址
/*客户端程序发起连接请求*/
if(connect(sockfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr)) == -1){
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
exit(1);
}
/*连接成功了*/
printf("Plese input char:\n");
/*发送数据*/
fgets(buffer,1024,stdin);
write(sockfd,buffer,strlen(buffer));
//read_data_from_common_sockfd(sockfd);
read_data_from_select_sockfd(sockfd);
/*结束通讯*/
close(sockfd);
}
④TCP并发服务器
并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是有服务器创建一个子进程来处理:
socket(…);
bind(…);
listen(…);
while(1){
accept(…);
if(fork(…) == 0){
process(…);
close(…);
exit(…);
}
close(…);
}
⑤TCP并发服务器实例分析与演示
Tcp_server_fork.c 结合网络分析工具tcp_dump,重点是讲怎么用tcpdump抓包文件格式为.pcap以后,怎么用wireshark分析这个文件
Tcp_server_fork.c
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define MY_PORT 3333
int main(int argc,char *argv)
{
int listen_fd,accept_fd;
struct sockaddr_in client_addr;
int n;
int nbytes;
if((listen_fd = socket(AF_INET,SOCK_STREAM,0)) < 0){ //AF_INET:IPV4; SOCK_STREAM:TCP;
printf("Socket Error: %s\n\a",strerror(errno));
exit(1);
}
bzero(&client_addr,sizeof(struct sockaddr_in)); //初始化 置0
client_addr.sin_family = AF_INET; //AF_INET : IPV4
client_addr.sin_port = htons(MY_PORT); //将本机端口号转化成short
client_addr.sin_addr.s_addr = htonl(INADDR_ANY); //绑定任何一个IP地址
//server_addr.sin_addr.s_addr = inet_addr("192.168.1.1"); //用于绑定到一个固定IP,这里使用了inet_addr函数
n = 1;
/*如果服务器终止后,服务器可以第二次快速启动而不用等待一段时间*/
setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(int));
if(bind(listen_fd,(struct sockaddr*)&client_addr,sizeof(client_addr)) < 0){
printf("Bind Error:%s\n\a",strerror(errno));
exit(1);
}
listen(listen_fd,5);
while(1){
accept_fd = accept(listen_fd,NULL,NULL);
/*EINTR means that a syscall is interrupted by a signal. You should restart it if you want it to work properly as the signal doesnt exist.*/
/* 慢系统调用(slow system call):此术语适用于那些可能永远阻塞的系统调用。永远阻塞的系统调用是指调用有可能永远无法返回,多数网络支持函数都属于这一类。如:若没有客户连接到服务器上,那么服务器的accept调用就没有返回的保证。
EINTR错误的产生:当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。例如:在socket服务器端,设置了信号捕获机制,有子进程,当在父进程阻塞于慢系统调用时由父进程捕获到了一个有效信号时,内核会致使accept返回一个EINTR错误(被中断的系统调用)。*/
if((accept_fd < 0) && (errno == EINTR)){
continue;
}else if(accept_fd < 0){
printf("Accept Error:%s\n\a",strerror(errno));
continue;
}
if((n = fork()) == 0){
/*子进程处理客户端连接*/
char buffer[1024];
if((nbytes = read(accept_fd,buffer,1024)) == -1){
fprintf(stderr,"Read Error:%s\n",strerror(errno));
exit(1);
}
buffer[nbytes] = '\0';
printf("Server received %s\n",buffer);
close(listen_fd);
close(accept_fd);
exit(0);
}else{
close(accept_fd);
}
}
}
tcp_client.c 多客户端
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define BUFSIZE 512
#define MY_PORT 3333
/*堵塞读*/
void read_data_from_common_sockfd(int sockfd)
{
char text[BUFSIZE] = {'\0'};
FILE * pf;
pf = fopen("1.txt","a+");
while(recv(sockfd,text,BUFSIZE,0)>0){
printf("client recv:%s\n",text);
fputs(text,pf);
memset(text,'\0',BUFSIZE);
}
fclose(pf);
}
/*非阻塞读*/
void read_data_from_select_sockfd(int sockfd)
{
fd_set fdset;
char read_buf[512] = {'\0'};
while(1){
int flag = 0;
int ret = 0;
struct timeval timeout = {2,0};
//timeout.tv_sec = 2;
//timeout.tv_usec = 0;
FD_ZERO(&fdset);
FD_SET(sockfd, &fdset);
ret = select(sockfd + 1, &fdset, NULL, NULL, &timeout);
if(ret <= 0){
continue;
}else{
if(FD_ISSET(sockfd, &fdset)){
int tmp_read = 0, all_read = 0;
while((tmp_read = read(sockfd, read_buf + all_read, sizeof(read_buf))) != 0)
all_read += tmp_read;
printf("select client recv:%s\n", read_buf);
break;
}
}
}
}
int main(int argc,char *argv[])
{
int sockfd;
char buffer[1024];
struct sockaddr_in server_addr;
struct hostent *host;
if(argc != 2){
fprintf(stderr,"Usage:%s hostname \a\n",argv[0]);
exit(1);
}
if((host = gethostbyname(argv[1])) == NULL){
fprintf(stderr,"Gethostbyname error\n");
exit(1);
}
/*客户程序开始建立 sockfd描述符*/
if((sockfd=socket(AF_INET,SOCK_STREAM,0)) == -1){ //AF_INET:IPV4 internet; SOCK_STREAM :TCP
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
exit(1);
}
/*客户程序填充服务端的资料*/
bzero(&server_addr,sizeof(server_addr)); //初始化,置0
server_addr.sin_family = AF_INET; //IPV4
server_addr.sin_port = htons(MY_PORT); //将本机器上的short数据转化为网络上的short数据
server_addr.sin_addr = *((struct in_addr *) host->h_addr); //IP地址
/*客户端程序发起连接请求*/
if(connect(sockfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr)) == -1){
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
exit(1);
}
/*连接成功了*/
printf("Plese input char:\n");
/*发送数据*/
fgets(buffer,1024,stdin);
write(sockfd,buffer,strlen(buffer));
//read_data_from_common_sockfd(sockfd);
read_data_from_select_sockfd(sockfd);
/*结束通讯*/
close(sockfd);
}
六、HTTP程序设计
①HTTP简介
. HTTP(Hypertext Transfer Protocol)超文本传输协议,它是web的核心,在[RFCF1945]和[RFC 2616]中进行定义。
. HTTP由两个程序实现:一个客户端程序和一个服务器程序。客户程序和服务器程序运行在不同的端系统中,通过交换HTTP报文进行会话
.HTTP是一个基于请求/响应模式的、无状态的协议。
.目前常用版本是HTTP1.0 和 HTTP1.1 http2和http3大部分互联网平台已经支持
②HTTP持续与非持续连接
嵌入到网页中的超文本概念可能需要多个请求和应答。如果网页,这个被获取的对象,位于不同的服务器,那么我们没有其他选择只能每获取一个对象就要创建一个新的TCP连接。然而,如果某些对象是位于同一台服务器的,我们可以有两种选择:一是每次使用一个新的TCP连接获取一个对象。二是创建一个TCP连接获取全部对象。第一种方法称为非持续连接,第二种方法称为持续连接
③HTTP与URL
总结: URL 它定义了相关网页的地址和名称。
每个URL地址有两部分组成:存放对象的服务器主机名和对象的路径。
例如:
URL地址http://www.someSchool.edu/someDepartment/picture.gif,其中 http为协议,www.someSchool就是主机名,没写端口号为默认的80/someDepartment/picture.gif就是路径
这也是遵守Restful规则
④HTTP URL Restful接口规则
就是在你的url地址里面要能看到分类,比如查询用户信息类的都用/userxxx/xxx,查询公司信息类的就用/companyxx/xxx类似这样
例如:
http://ip:端口/v2/userInfo?userName=cao
http://ip:端口/v2/companyInfo?departName=开发部
⑤HTTP报文格式和响应报文
⑥HTTP请求—请求行
请求行一个方法符号开头,后面跟着请求URL和协议的版本,以CRLF(\r\n)作为结尾。请求行以空格分隔,除了作为结尾的CRLF外,不允许出现单独的CR或者LF字符。格式如下
Method Request-URL HTTP-Version CRLF
Method表示请求的方法,Request-URI是一个统一资源标识符,标识了要请求的资源,HTTP-Version表示请求的HTTP协议版本,CRLF表示回车换行。例如:
GET /form.html HTTP/1.1 (CRLF)
⑦HTTP请求-方法GET
.GET方法用于获取由Request-URL所标识的资源的信息,常见的形式是:
GET Request-URL HTTP/1.1 (CRLF)
.当我们通过在浏览器的地址栏中直接输入网址的方式去访问网页的时候,浏览器采用的就是GET方法向服务器获取资源
HTTP 方法:GET 对比 POST 网址:
http://www.w3school.com.cn/tags/html_ref_httpmethods.asp
⑧HTTP请求—方法POST
.POST方法用于向目的服务器发出请求,要求服务器接受附在请求后面的数据。POST方法在表单提交的时候用得较多。
.采用POST方法提交表单的例子
POST /reg.jsp HTTP/1.1 (CRLF)
Accept: image/gif …..(因为篇幅关系省略) (CRLF)
……因为篇幅关系省略
Host: www.winsunlight.com (CRLF)
Content-Length: 22 (CRLF)
Connection: Keep-Alive (CRLF)
Cache-Control: no-cache (CRLF)
(CRLF)
user=zhangsan&pwd=1234
⑨HTTP请求 –方法HEAD
HEAD方法与GET方法几乎是一样的,它们的区别在于HEAD方法只是请求消息报头,而不是完整的内容。对于HEAD请求的回应部分来说,它的HTTP头部中包含的信息与通过GET请求所得的信息是相同的。利用这个方法,不必传输整个资源的内容,就可以得到Request-URL所标识的资源的信息。这个方法通常被用于测试超链接的有效性,是否可以访问,以及最近是否更新。
⑩HTTP响应—状态行
. 状态行由协议版本、数字形式的状态代码、及相应的状态描述组成,各元素之间以空格分隔,除了结尾的CRLF(回车换行)序列外,不允许出现CR或LF字符。格式如下:
HTTP-Version Status-Code Reason-Phrase CRLF
HTTP-Version表示服务器HTTP协议的版本,Status-Code表示服务器发回的响应代码,Reason-Phrase表示状态代码的文本描述,CRLF表示回车换行。例如:
HTTP/1.1 200 OK (CRLF)
11、HTTP响应—状态代码与状态描述(1)
.状态代码由3位数字组成,表示请求是否被理解或者被满足,状态描述给出了关于状态代码的简短的文本描述。
.状态代码的第一个数字定义了响应的类别,后面两位数字没有具体的分类。第一个数字有五种可能的取值:
-- 1xx: 指示信息 ---表示请求已接收,继续处理
-- 2xx: 成功---表示请求已经被成功接收、理解、接受。
-- 3xx:重定向---表示要完成请求必须进行更进一步的操作。
-- 4xx:客户端错误--- 表示请求有语法错误或请求无法实现
-- 5xx:服务器端错误---服务器未能实现合法的请求。
12、HTTP响应—状态代码与状态描述(2)
一些常见的状态码和相关的短语包括:
. 200 OK : 请求成功,信息在返回的响应报文中。
.301 Moved Permanetly : 请求的对象已经被永久转移,新的URL定义在响应报文的Location:首部行中。客户软件将自动获取新的URL。
.400 Bad Requset: 一个通用差错代码,指示该请求不能被服务器理解。
.404 Not Found: 被请求的文档不在服务器上。
.505 HTTP Version Not Supported: 服务器不支持请求报文使用的HTTP协议版本。
13、HTTP实例分析
tcp_client_http.c
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define BUFSIZE 2048
int creat_socket(char *server_ip,int server_port);
void send_data_to_sockfd(int sockfd,char send_buf[],int send_buf_len);
int read_data_from_common_sockfd(int sockfd);
void make_http_requset_header(int sockfd,char *url,char *server_ip,char *server_port);
/*创建套接字并且连接到服务器*/
int creat_socket(char *server_ip,int server_port)
{
int sockfd = 0;
struct sockaddr_in server_addr;
struct hostent *host;
if((host = gethostbyname(server_ip)) == NULL){
fprintf(stderr,"Gethostbyname error\n");
exit(1);
}
/*客户程序开始建立 sockfd描述符*/
if((sockfd=socket(AF_INET,SOCK_STREAM,0)) == -1){ //AF_INET:IPV4 internet; SOCK_STREAM :TCP
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
exit(1);
}
/*客户程序填充服务端的资料*/
bzero(&server_addr,sizeof(server_addr)); //初始化,置0
server_addr.sin_family = AF_INET; //IPV4
server_addr.sin_port = htons(server_port); //将本机器上的short数据转化为网络上的short数据
server_addr.sin_addr = *((struct in_addr *) host->h_addr); //IP地址
/*客户端程序发起连接请求*/
if(connect(sockfd,(struct sockaddr*)(&server_addr),sizeof(struct sockaddr)) == -1){
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
exit(1);
}
return sockfd;
}
/*向socket中发送数据*/
void send_data_to_sockfd(int sockfd,char send_buf[],int send_buf_len)
{
int tmp_send = 0, all_send = 0;
while((tmp_send = send(sockfd, send_buf + all_send, strlen(send_buf) - all_send,0)) > 0)
all_send += tmp_send;
}
/*从socket中非阻塞方式读数据*/
int read_data_from_common_sockfd(int sockfd)
{
char readbuf[BUFSIZE] = {'\0'};
int fd;
fd = open("./1.txt", O_WRONLY|O_CREAT);
//snprintf(readbuf, sizeof(readbuf) - 1, "%s", "echo " " > 1.txt"); //清空1.txt
//system(readbuf);
//memset(readbuf,'\0',BUFSIZE);
while(recv(sockfd,readbuf,BUFSIZE,0)>0){
write(fd, readbuf, BUFSIZE);
}
close(fd);
printf("client recv:%s\n",readbuf);
char *p = strstr(readbuf, "\r\n\r\n");
if(!p)
{
printf("read_buf no '\\r\\n\\r\\n' \n");
return -1;
}
memset(readbuf,'\0',BUFSIZE);
return 0;
}
/*打包http的头文件并发送*/
void make_http_requset_header(int sockfd,char *url,char *server_ip,char *server_port)
{
char header_buf[BUFSIZE] = {'\0'};
//strnset(header_buf,'/0',BUFSIZ);
printf("make http requset header ...\n");
strcat(header_buf,"GET / ");
if(url){
strcat(header_buf,url);
}
strcat(header_buf,"HTTP/1.1\r\n");
strcat(header_buf,"Accept: */*\r\n");
strcat(header_buf,"Accept-Language: zh-cn\r\n");
strcat(header_buf,"User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729)\r\n");
strcat(header_buf,"Accept-Encoding: gzip, deflate\r\n");
strcat(header_buf,"Host:");
strcat(header_buf,server_ip);
strcat(header_buf,":");
strcat(header_buf,server_port);
//strcat(header_buf,"\r\nConnection: Keep-Alive\r\n"); //服务器会一直阻塞不发送close,客户机可以继续发送数据
strcat(header_buf,"\r\nConnection: Close\r\n"); //服务器处理完一次数据就会close,客户机必须重新建立连接才能发送数据
strcat(header_buf,"\r\n");
printf("header_buf: %s\n",header_buf);
send_data_to_sockfd(sockfd,header_buf,strlen(header_buf));
}
int main(int argc,char *argv[])
{
int sockfd;
int port;
if(argc != 3){
fprintf(stderr,"Usage:%s hostname \a\n",argv[0]);
exit(1);
}
port = atoi(argv[2]);
/*连接成功了*/
sockfd = creat_socket(argv[1],port);
printf("creat socket sucessful!!!:\n");
/*发送数据*/
make_http_requset_header(sockfd,NULL,argv[1],argv[2]);
/*接收数据*/
read_data_from_common_sockfd(sockfd);
/*关闭网络连接*/
close(sockfd);
}
访问公司服务器 192.168.32.62 端口号 8280
例如:打开两个终端
第一个终端 :
Tcpdump -ni eth1 -s 0 –w xxxx.pcap
第二个终端:
./tcp_client_http 192.168.32.62 8280
用wireshark分析xxxx.pcap分析
14、HTTP数据通信JSON
简单来说:用大括号表示的字符串结构体
分析两个程序:
1.cjson_test1
2.cjson_test2
下载链接:
https://download.csdn.net/download/caofengtao1314/12491681
七、参考资料
Wireshark使用网站:http://www.cr173.com/html/20128_all.html
Tcpdump使用网站:
http://blog.csdn.net/langeldep/article/details/6156818
http://gaodi2002.blog.163.com/blog/static/232076820061023113452176/
参考书:TCP/IP详解第一卷
UNIX网络编程第一卷
UNIX环境高级编程