一、IO多路复用并发服务器模型(看相关解读文件夹)
select.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/select.h>
int main(int argc, const char *argv[])
{
int listenfd, connfd;
int ret;
struct sockaddr_in seraddr, cliaddr;
char buf[1024] = "\0";
socklen_t addrlen = sizeof(cliaddr);
int maxfd;
int i;
fd_set g_rdfs, c_rdfs;
//step 1: 创建套接字接口
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == listenfd)
{
perror("socket failed");
return -1;
}
//step 2: 绑定IP + Port
memset(&seraddr, 0, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(8888);
seraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = bind(listenfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if(-1 == ret)
{
perror("bind");
return -1;
}
//step 3:监听连接
ret = listen(listenfd, 5);
if(-1 == ret)
{
perror("listen");
return -1;
}
FD_ZERO(&g_rdfs);
FD_SET(listenfd, &g_rdfs);
maxfd = listenfd;
while(1)
{
c_rdfs = g_rdfs;
ret = select(maxfd + 1, &c_rdfs, NULL, NULL, NULL);
if(-1 == ret)
{
perror("select");
exit(-1);
}
else
{
for(i = 0; i <= maxfd; i++)
{
if(FD_ISSET(i, &c_rdfs))
{
if(listenfd == i)
{
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &addrlen);
if(-1 == connfd)
{
perror("accept");
return -1;
}
printf("connect successfully: client IP : %s, Port : %d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
FD_SET(connfd, &g_rdfs);
maxfd = maxfd > connfd ? maxfd : connfd;
}
else
{
ret = recv(i, buf, sizeof(buf), 0);
if(-1 == ret)
{
close(i);
FD_CLR(i, &g_rdfs);
}
else
{
ret = send(i, buf, sizeof(buf), 0);
if(-1 == ret)
{
close(i);
return -1;
}
}
}
}
}
}
}
close(listenfd);
return 0;
}
二、网络常用工具的使用(看相关解读文件夹)
(1)lsof(list open file)列举出当前系统所打开的文件
(1)lsof会访问内核打开的各种文件,所以需要root用户身份去运行
sudo lsof
(2)lsof -p <pid> 显示指定进程打开的文件
(3)lsof filename 显示指定文件在哪些进程中被打开
(4)lsof -d fd 显示所有拥有文件描述符是fd的进程
lsof -d 5
(5)lsof -i:端口号
lsof -i TCP
lsof -i @ip地址
(6)当内存中还有进程打开你意外删除的文件的时候,可以使用lsof恢复
lsof | grep filename //查看打开这个文件的进程
cd /proc/4068/fd //切换到/proc文件系统
cat fd > filename // 将删除的文件从内存倒回磁盘
(2)netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics)等(了解)
netstat -a 列举出所有的网络连接
netstat -at/-au 只列举tcp或udp
netstat -atln 显示绑定的ip地址
netstat -atlnp ……并显示进程号
netstat -s 打印每个协议使用的情况
netstat -ts/ netstat -us
netstat -ie 等价于ifconfig
(3)telnet 检测TCP服务是否能够正常使用(万能客户端,常用网络测试)
检测tcp 相应端口号是否可以正常使用
telnet 127.0.0.1 8888(万能的客户端)
(4)wireshark 网络抓包工具
(5)tcpdump 网络抓包工具(当你的Linux无法使用图形化界面的时候)
监视指定网络接口的数据包:
sudo tcpdump -i lo
如果不指定网卡,默认tcpdump只会监视第一个网络接口,一般是eth0,下面的例子都没有指定网络接口。
指定过滤数据报的类型
sudo tcpdump tcp -i lo
抓取完整的数据包
抓取数据包时默认抓取长度为68字节。加上-s 0 后可以抓到完整的数据包
sudo tcpdump tcp -i lo -s 0
和wireshark结合起来使用,用tcpdump抓包,用wireshark查看,
保存成cap文件,方便用ethereal(即wireshark)分析
sudo tcpdump tcp -i lo -s 0 -w ./1.cap
三、常见协议头分析
(1)TCP包头
[1]源端口号:该报文段是哪个服务发出的
[2]目的端口号:该报文段需要哪个服务接收。
[3]序号(sequence number):
一次TCP通信(从TCP连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。
随机值ISN(Initial Sequence Number,初始序号值)。
后续的TCP报文段中序号值将被系统设置成ISN加上该报文段所携带数据的第一个字节在整个字节流中的偏移。
[4]确认号(acknowledgement number):
用作对另一方发送来的TCP报文段的响应。其值是期待下一次收到的TCP报文段的序号值。
[5]头部长度(header length):
其值表示该TCP头部有多少个32bit字(4字节)。因为4位最大能表示15,所以TCP头部最长是60字节。
[6]标志位(TCP flag):
ACK标志:表示确认号是否有效(ACK标志位置1),表示该数据包是对收到数据的一种回应。
PSH标志:
提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间
SYN标志:表示请求建立一个连接。(三次握手)
注意:携带SYN标志位的TCP数据包,协议规定长度为1
只携带ACK标志位的TCP数据包(该数据仅仅只是回应),协议规定长度为0
(2)UDP包头
udp的报length是1比特代表一字节---2^16字节==64kb ,而tcp的表头length是1比特代表一字(4字节)---2^4字 ==60字节;
为什么不统一呢??
(3)IP包头
TTL(TIME TO LIVE)存时间字段设置了数据报可以经过的最多路由器数(数据包没经过一个路由器TTL的值就会被减1,TTL值被减到0时,
该数据包就会被丢弃)
协议(Protocol,PROT),指该封包携带的上层网络协议类型
TTL的存在是为了不让全网瘫痪
(4)以太网包头
所用协议:用于标识上层协议
0800 IP
0806 ARP
8035 RARP
网络数据是多个包连续的,这些包怎末划分的??
四、套接字选项设置(网络属性设置)
fcntl (文件设置)
setsockopt (套接字设置)
网络通信的实现涉及多个层次,通过设置不同层次的套机字属性可以实现不同的功能
setsockopt
level : 按照协议分层,与协议没有任何关系的归到SOL_SOCKET(通用套接字选项)
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
(1)获取套接字选项
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
- sockfd : 套接字描述符
- level : 套接字选项所属层 SOL_SOCKET IPPROTO_IP IPPRO_TCP
- optname: 套接字选项(具体选项参考讲义) SO_BROADCAST
- optval : 保存套接字选项值的缓冲区首地址
- optlen : 保存选项值的长度
返回值
成功 0
失败 -1并设置出错码
(2)设置套接字选项
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
- sockfd : 套接字描述符
- level : 套接字选项所属层
- optname: 套接字选项(具体选项参考讲义)
- optval : 需要设置的套接字选项值的缓冲区首地址
- optlen : 选项值的长度
返回值
成功 0
失败 -1并设置出错码
注意:
- level :
因为套接字的选项有很多,为了便于管理所以根据,套接字选项被定义的具体层次来分类。
- 参考讲义我们发现套接字选项的具体值的数据类型不一样
主要因为套接字选项可粗略的分为两大类:
一类用于开启或关闭某个特性称为(标志选项),这一类大多对应int,开启某项功能置1,关闭置0。
另一类可以设置特定的值我们称为(值选项),这类选项对应特定的结构体。
- 通用套接字选项SOL_SOCKET,这一类选项实现和具体协议无关(换句话说,它们是内核中的协议无关代码实现的,而不是诸如IPV4之类协议去定义的),可以适用于不同的协议和不同协议层。
五、网络超时检测
- 在网络通信中,很多操作会使得进程阻塞
- TCP套接字中的recv/accept/connect
- UDP套接字中的recvfrom
- 超时检测的必要性:
避免进程在没有数据时无限制地阻塞,
当设定的时间到时,进程从原操作返回继续运行。
1.通过设置套接字选项(SO_RCVTIMEO)接受超时看上面的套接字选项设置
一次设置终身受用
struct timeval tv;
tv.tv_sec = 5; // 设置5秒时间
tv.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); // 设置接收超时
recv() / recvfrom() // 从socket读取数据
如果这个sockfd是SOCK_STREAM的话,
要先绑定,再监听,然后接受请求,顺序不能乱
为什吗?
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
2.select检测是否超时 -----多路复用有等待时间设置
select参数中自带时间检测(多路复用)!
在Linux中,select会修改时间,所以每次用都要设置
3.设置定时器alarm SIGALRM(终止进程)
sigaction--------这个系统调用的作用是改变进程接收到的指定信号的行动
signal函数:作用1:站在应用程序的角度,注册一个信号处理函数 作用2:忽略信号,设置信号默认处理 信号的安装和回复
注意:一个进程只能有一个闹钟,新闹钟会取代旧的闹钟
alarm(0) 取消闹钟
闹钟加signal();??不对,看下面
信号掩码
STGCHLD的来源有很多的,看红字
这里没有用掩码!!用的sa_flags!!???sa_flags信号处理选项与那三个表无关
清标志
SA_RESTART 0000 0100
1111 1011
flag = 0100 1000
flag = flag | SA_RESTART = 0100 1100
flag = flag & ~SA_RESTART = 0100 1000