#相关概念
(1)协议:一组规则
(2)OSI七层模型;TCP/IP四层模型
应用层协议:http;ftp;nfs;ssh;telnet
传输层协议:TCP;UDP
网络层协议:IP;ICMP;IGMP
链路层协议:以太网帧协议(根据MAC地址,完成数据包传输);ARP(根据IP地址获取MAC 地址)
(3)网络传输流程:数据没有封装之前,不能在网络中传递
(4)IP地址:可以在网络环境中,唯一标识一台主机;
端口号:可以在网络的一台主机上,唯一标识一个进程;
IP地址+端口号:可以在网络环境中,唯一标识一个进程;
UDP协议:16位源端口号,16位目的端口号;
IP协议:16位源端口号,16位目的端口号;32位序号,32位确认序号;6个标志位;16位窗口大小
(5)网络套接字socket:
在通信过程中,套接字一定是成对出现的;
一个文件描述符指向一个套接字,该套接字内部由内核借助两个缓冲区实现。
(6)C/S客户端服务器模型和B/S网页服务器模型:
C/S优点:缓存大量数据,协议选择灵活,速度快
C/S缺点:安全性,跨平台,开发工作量大
B/S优点:安全性,跨平台,开发工作量小
B/S缺点:不能缓存大量数据,严格遵守http
(7)以太网帧协议:
ARP协议:根据IP地址获取MAC地址
以太网帧协议:根据MAC地址,完成数据包传输
IP协议:
版本:IPV4、IPV6
TTL:设置数据包在路由节点中的跳转上限,每经过一个路由节点,该值减一
源IP:32位
目的IP:32位
(8)网络字节序:
小端法:(pc本地存储) 高位存高地址,低位存低地址;
大端法:(网络存储) 高位存低地址,低位存高地址;
htonl 本地》网络IP
htons 本地》网络PORT
ntohl 网络》本地IP
ntohs 网络》本地PORT
(9)TCP通信流程分析:
server:socket();bind();listen();accept();read(fd);do;write(fd);close()
client:socket();connect();write();read();显示;close()
(10)三次握手:
主动发起连接请求端,发送SYN标志位,请求建立连接。携带序号号、数据字节数(0)、滑动窗口大小;
被动接受连接请求端,发送ACK标志位,同时携带SYN请求标志位。携带序号、确认序号、数据字节数(0)、滑动窗口大小;
主动发起连接请求端,发送ACK标志位,应答服务器连接请求。携带确认序号。
(11)四次挥手:
主动关闭连接请求端,发送FIN标志位;
被动关闭连接请求端,应答ACK标志位; ------半关闭完成
被动关闭连接请求端,发送FIN标志位;
主动关闭连接请求端,应答ACK标志位。 ------连接全部关闭
(12)滑动窗口:
发送给连接对端,本端缓冲区大小(实时),保证数据不会丢失。
(13)TCP状态时序图
1、主动发起连接请求端:CLOSE–发送SYN–SEND_SYN–接收ACK、SYN–SEND_SYN–发送ACK–ESTABLISHED(数据通信态)
2、主动关闭连接请求端:ESTABLISHED(数据通信态)–发送FIN–FIN_WAIT1–接收ACK–FIN_WAIT2(半关闭)–接收对端发送FIN–FIN_WAIT2(半关闭)–回发ACK–TIME_WAIT–TIME_WAIT(只有主动关闭连接方会经历该状态)–等2MSL时长–CLOSE
3、被动接收连接请求端:CLOSE–LISTEN–接收SYN–LISTEN–发送ACK、SYN–SYN_RCVD–接收ACK–ESTABLISHED(数据通信态)
4、被动关闭连接请求端:ESTABLISHED(数据通信态)–接收FIN–ESTABLISHED(数据通信态)–发送ACK–CLOSE_WAIT(说明对端处于半关闭状态)–发送FIN–LAST_ACK–接收ACK–CLOSE
(14)select多路IO转换也可以实现C/S模式通信,原理是借助内核,使用select来监听客户端连接和数据通信事件
C/S模式服务器代码示例:
#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#define SERV_PORT 9527
void sys_err(const char *str){
perror(str);
exit(1);
}
int main(int argc,char *argv[]){
int lfd=0,cfd=0;
int ret,i;
char buf[BUFSIZ],client_IP[1024];
struct sockaddr_in serv_addr,clit_addr;
socklen_t clit_addr_len;
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERV_PORT);
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
lfd=socket(AF_INET,SOCK_STREAM,0);
if(lfd==-1)
sys_err("socket error");
bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
listen(lfd,128);
clit_addr_len=sizeof(clit_addr);
cfd=accept(lfd,(struct sockaddr *)&clit_addr,&clit_addr_len);
if(cfd==-1)
sys_err("accept error");
printf("client ip:%s,port:%d\n",inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),ntohs(clit_addr.sin_port));
while(1){
ret=read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
for(i=0;i<ret;i++)
buf[i]=toupper(buf[i]);
write(cfd,buf,ret);
}
close(lfd);
close(cfd);
return 0;
}
C/S模式客户端代码示例:
#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#define SERV_PORT 9527
void sys_err(const char * str){
perror(str);
exit(1);
}
int main(int argc,char *argv[]){
int cfd;
int conter=10;
char buf[BUFSIZ];
struct sockaddr_in serv_addr;
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERV_PORT);
inet_pton(AF_INET,"113.54.205.185",&serv_addr.sin_addr.s_addr);
cfd=socket(AF_INET,SOCK_STREAM,0);
if(cfd==-1)
sys_err("socket error");
int ret=connect(cfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
if(ret!=0)
sys_err("connect error");
while(--conter){
write(cfd,"hello",5);
sleep(1);
ret=read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
}
close(cfd);
return 0;
}
多进程通信服务器代码示例
#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#define SRV_PORT 9999
void catch_child(int signum){
while(waitpid(0,NULL,WNOHANG)>0);
return;
}
int main(int argc,char *argv[]){
int lfd,cfd;
pid_t pid;
struct sockaddr_in srv_addr,clt_addr;
socklen_t clt_addr_len;
char buf[BUFSIZ];
int ret,i;
bzero(&srv_addr,sizeof(srv_addr)); //将地址结构清零
srv_addr.sin_family=AF_INET;
srv_addr.sin_port=htons(SRV_PORT);
srv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
lfd=socket(AF_INET,SOCK_STREAM,0);
int opt=1; //设置端口复用
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,(void *)&opt,sizeof(opt)); //设置端口复用
bind(lfd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));
listen(lfd,128);
clt_addr_len=sizeof(clt_addr);
while(1){
cfd=accept(lfd,(struct sockaddr *)&clt_addr,&clt_addr_len);
pid=fork();
if(pid<0)
perror("fork error");
else if(pid==0){
close(lfd);
for(;;){
ret=read(cfd,buf,sizeof(buf));
for(i=0;i<ret;i++)
buf[i]=toupper(buf[i]);
write(cfd,buf,ret);
write(STDOUT_FILENO,buf,ret);
// ret=read(cfd,buf,sizeof(buf));
if(ret==0){
close(cfd);
exit(1);
}
}
}
else{
struct sigaction act;
act.sa_handler=catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
ret=sigaction(SIGCHLD,&act,NULL);
if(ret!=0)
perror("sigaction error");
close(cfd);
continue;
}
}
return 0;
}
多线程通信服务器代码示例
#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<fcntl.h>
#define MAXLINE 8192
#define SERV_PORT 8000
struct s_info{ //定义一个结构体,将地址结构跟cfd捆绑
struct sockaddr_in cliaddr;
int connfd;
};
void *do_work(void *arg){
int n,i;
struct s_info *ts=(struct s_info*)arg;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
while(1){
n=read(ts->connfd,buf,MAXLINE);
if(n==0){
printf("the client %d closed...\n",ts->connfd);
break;
}
for(i=0;i<n;i++)
buf[i]=toupper(buf[i]);
write(STDOUT_FILENO,buf,n);
write(ts->connfd,buf,n);
}
close(ts->connfd);
return (void *)0;
}
int main(void){
struct sockaddr_in servaddr,cliaddr;
socklen_t cliaddr_len;
int listenfd,connfd;
pthread_t tid;
struct s_info ts[256];
int i=0;
listenfd=socket(AF_INET,SOCK_STREAM,0);
int opt=1; //设置端口复用
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,(void *)&opt,sizeof(opt)); //设置端口复用
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
listen(listenfd,128);
printf("accepting client connect...\n");
while(1){
cliaddr_len=sizeof(cliaddr);
connfd=accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
ts[i].cliaddr=cliaddr;
ts[i].connfd=connfd;
pthread_create(&tid,NULL,do_work,(void*)&ts[i]);
pthread_detach(tid);
i++;
}
return 0;
}
select实现C/S通信服务器代码示例
#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#define SERV_PORT 6666
int main(int argc,char *argv[]){
int lfd,cfd;
char buf[BUFSIZ];
struct sockaddr_in clie_addr,serv_addr;
socklen_t clie_addr_len;
lfd=socket(AF_INET,SOCK_STREAM,0);
int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //设置端口复用
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(SERV_PORT);
bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
listen(lfd,128);
fd_set rset,allset; //定义读集合,备份集合allset
int ret,maxfd=0,n,i,j;
maxfd=lfd; //最大文件描述符
FD_ZERO(&allset); //清空 监听集合
FD_SET(lfd,&allset); //将待监听的lfd添加到监听集合中
while(1){
rset=allset; //备份
ret=select(maxfd+1,&rset,NULL,NULL,NULL); //使用select监听
if(ret<0)
perror("select error");
if(FD_ISSET(lfd,&rset)){ //lfd满足监听的读事件
clie_addr_len=sizeof(clie_addr);
cfd=accept(lfd,(struct sockaddr *)&clie_addr,&clie_addr_len);//建立连接,不会阻塞
FD_SET(cfd,&allset); //将新产生的fd添加到监听集合中,监听数据读事件
if(maxfd<cfd) //修改最大文件描述符
maxfd=cfd;
if(ret==1)
continue;
}
for(i=lfd+1;i<=maxfd;i++){ //处理满足读事件的fd
if(FD_ISSET(i,&rset)){
n=read(i,buf,sizeof(buf));
if(n==0){ //检测到客户端已经关闭连接
close(i);
FD_CLR(i,&allset); //将关闭的fd,移除监听集合
}
else if(n==-1)
perror("read error");
else{
for(j=0;j<n;j++)
buf[j]=toupper(buf[j]);
write(i,buf,n);
write(STDOUT_FILENO,buf,n);
}
}
}
}
close(lfd);
return 0;
}
select实现C/S通信服务器(使用数组优化遍历版本)代码示例
#include<stdio.h>
#include<ctype.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#define SERV_PORT 6666
int main(int argc,char *argv[]){
int i,j,n,maxi;
int ret,client[FD_SETSIZE]; //FD_SETSIZE默认为1024
int maxfd,lfd,cfd,sockfd;
char buf[BUFSIZ],str[INET_ADDRSTRLEN]; //INET_ADDRSTRLEN 16
fd_set rset,allset;
struct sockaddr_in clie_addr,serv_addr;
socklen_t clie_addr_len;
lfd=socket(AF_INET,SOCK_STREAM,0);
int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //设置端口复用
bzero(&serv_addr,sizeof(serv_addr));
serv_addr.sin_family=AF_INET;
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_addr.sin_port=htons(SERV_PORT);
bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
listen(lfd,128);
maxfd=lfd; //最大文件描述符
maxi=-1; //将来用作client[]的下标,初始值指向0元素之前下标位置
for(i=0;i<FD_SETSIZE;i++)
client[i]=-1;
FD_ZERO(&allset); //清空 监听集合
FD_SET(lfd,&allset); //将待监听的lfd添加到监听集合中
while(1){
rset=allset; //备份
ret=select(maxfd+1,&rset,NULL,NULL,NULL); //使用select监听
if(ret<0)
perror("select error");
if(FD_ISSET(lfd,&rset)){ //lfd满足监听的读事件
clie_addr_len=sizeof(clie_addr);
cfd=accept(lfd,(struct sockaddr *)&clie_addr,&clie_addr_len);//建立连接,不会阻塞
printf("received from %s at PORT %d\n",inet_ntop(AF_INET,&clie_addr.sin_addr,str,sizeof(str)),ntohs(clie_addr.sin_port));
for(i=0;i<FD_SETSIZE;i++)
if(client[i]<0){
client[i]=cfd; //找出client[]中没有使用的位置
break; //保存accept返回的文件描述符到client[]里
}
if(i==FD_SETSIZE){ //达到select能监控的文件个数上限1024
fputs("too many clients\n",stderr);
exit(1);
}
FD_SET(cfd,&allset); //将新产生的fd添加到监听集合中,监听数据读事件
if(maxfd<cfd) //修改最大文件描述符
maxfd=cfd;
if(i>maxi) //保证maxi存的总是client[]最后一个元素下标
maxi=i;
if(ret==1)
continue;
}
for(i=0;i<=maxi;i++){ //检测哪个client有数据就绪
if((sockfd=client[i])<0)
continue;
if(FD_ISSET(sockfd,&rset)){
n=read(sockfd,buf,sizeof(buf));
if(n==0){ //检测到客户端已经关闭连接
close(sockfd);
FD_CLR(sockfd,&allset); //将关闭的fd,移除监听集合
client[i]=-1;
}
else if(n==-1)
perror("read error");
else{
for(j=0;j<n;j++)
buf[j]=toupper(buf[j]);
write(sockfd,buf,n);
write(STDOUT_FILENO,buf,n);
}
}
}
}
close(lfd);
return 0;
}