Linux网络编程复习笔记
理论知识点
TCP协议的三次握手的触发API
- connect : 第一次握手信息
- 第二次,第三次都是被动处理,无需调用接口
- accept不参与三次握手,取出三次握手成功的描述符
socket描述符的合理大小
属于linux
系统描述符fd
的一部分:
f
d
∈
[
0
,
1
,
2
…
…
]
fdin[0,1,2……]
fd∈[0,1,2……]
总之
f
d
>
=
0
fd>=0
fd>=0
把socket状态从主动发送状态转变为被动监听状态
listen + TCP的描述符
send(sockfd, buf, 5, 0)
表示的是向文件描述符为sockfd
的目的地址发送以buf
为首地址,5个字节的内容
端口号的范围
unsigned short 16bit [0 , 65535]
大端模式,小端模式
条件
大端:0x12345678 低位地址 0x12
小端:0x12345678 低位地址 0x78
int空间的传递
网络序 :大端
CPU序 :大/小端
-
htonl()
–“Host to Network Long” 将一个无符号长整型数值转换为网络字节序 -
ntohl()
–“Network to Host Long” 将一个网络字节序转换为无符号长整型数值 -
htons()
–“Host to Network Short” 将一个无符号短整型数值转换为网络字节序 -
ntohs()
–“Network to Host Short” 将一个网络字节序转换为无符号短整型数值
服务器编程模型中独有的API
socket
、bind
、send
、recv
connect
客户端
listen
、accept
服务器
HTTP状态码:302 404
- 2xx : 成功
- 3xx : 特殊功能 302 重定向
- 4xx : 客户端请求错误,服务器拒绝服务 404 not found
- 5xx : 服务器服务异常
HTTP请求的动作包含哪些
HTTP请求组成
请求报文 = 请求行+请求头+请求数据
HTTP的9个请求动作
HTTP1.0:
-
GET
:请求指定页面的信息,并返回实体主体 -
POST
:向指定资源提交数据进行处理请求,数据存在请求体 -
HEAD
: 类似get,但不返回具体内容,用于获取报头
HTTP1.1:
-
PUT
:完整替换更新指定资源数据,没有就新增 -
DELETE
:删除指定资源的数据 -
PATCH
:部分更新指定资源的数据 -
OPTIONS
:允许客户端查看服务器的支持的http请求方法 -
CONNECT
:预留给能将连接改为管道的代理服务器 -
TRACE
:追踪服务器收到的请求,用于测试或诊断
查看端口号命令
netstat
大概这样:
多进程高并发服务器 fork调用的时机
接收到新连接后,单独开辟一个子进程独立处理这个新连接,也就是accept
之后
UDP的通信API
socket
bind
sendto
recvfrom
DNS解析
通过gethostbyname
函数即可解析
函数原型:
struct hostent *gethostbyname(const char *hostname);
通过输入域名我们可以将其解析,然后返回一个hostent
的封装数据,这个数据的结构如下:
struct hostent{
char *h_name; //official name
char **h_aliases; //alias list
int h_addrtype; //host address type
int h_length; //address lenght
char **h_addr_list; //address list
}
这样我们就能解析域名并得到真正的IP地址
TCP/IP通信 全双工
全双工通过一个socket文件描述符允许数据从两边同时发送和接收
这边总结一下其他的知识点:
单工
单工指的是数据只能从一方传输,并不能实现双方通信
例如:电视、广播、收音机等
半双工
半双工指的是数据能从两方传输,但是同一时间数据只能在一个方向传输
例如:对讲机
全双工
全双工指的是数据可以从两个方向同时传输
例如:电话
零散知识点
-
socket
不仅仅支持TCP/IP协议和本地进程间通信 - 如果系统TCP使用了80端口,那么UDP也可以使用80端口
- TCP在释放链接过程中会有四次挥手的过程
- HTTP协议是应用层协议,他传输层是基于TCP协议的
- 如果服务器不调用
accept
函数,那么三次握手不会失效 - 如果两台电脑能
ping
通,说明两台电脑的网络层是相通的。 -
epoll
性能比select
性能各有所长 - UDP编程中,需要指定对方的IP和端口号也能通信。
sendto
可以send
可以(connect后) - CPU有小端模式,也有大端模式,处理数据
-
Ctrl+C
组合键实际触发的信号是SIGINT
- 在HTTP1.1规范中,
OPTIONS
动作代表允许客户端查看服务器的性能 - 在
sockaddr_in
结构体中,sin_addr
中一般使用INADDR_ANY
宏代表可以接收任意本地设备的网络数据 -
inet_aton
函数可以将主机字节序结构的IP地址转换为网络字节序 - 在select结构中,
FD_ISSET()
宏用来判断文件描述符是否在文件集合中
如何设计高并发服务器
通过epoll
多路复用实现高并发服务器
优点
-
epoll
没有最大并发连接限制,上限是最大可打开文件的数目,一般这个数目和系统内存有关,但是考虑到服务器的内存一般不小,所以可以实现高并发 -
效率有明显提升,
epoll
对于句柄事件的选择和select
、poll
的遍历不同,epoll
是事件相应的,即:句柄事件来到后,立即选择出来,复杂度为常数级别O
(
1
)
O(1)
O(1),不需要遍历,内核将句柄通过红黑树保存,所以随着句柄数目的增多,IO的效率也不会随之线性下降(只是会稍微下降一点)
-
内存拷贝,
select
让内核把 FD 消息通知给用户空间的时候使用了内存拷贝的方式,开销较大,但是epoll
在这点上使用了共享内存的方式,这个内存拷贝也省略了。
实现机制
epoll
的设计和实现与select完全不同。epoll
通过在Linux内核中申请一个简易的文件系统,把原先的select/poll调用分成了3个部分:
- step1:
调用epoll_create()
建立一个epoll
对象(在epoll
文件系统中为这个句柄对象分配资源)
- step2:
调用epoll_ctl
向epoll
对象中添加这上万个连接的套接字
- step3:
调用epoll_wait
收集发生的事件的连接
只需要在进程启动时建立一个epoll
对象,然后在需要的时候向这个epoll
对象中添加或者删除连接。同时,epoll_wait
的效率也非常高,因为调用epoll_wait
时,并没有直接向操作系统复制这数万个连接的句柄数据,内核也不需要去遍历全部的连接。所以可以应对高并发的情况
用一个图来表明epoll处理的逻辑关系:
代码知识点
初始化监听描述符 怎么做?可以不考虑出错处理
- 创建
socket
- 填充自己信息,公布端口
struct sockaddr_in self
、bind
- 改变为监听状态
listen
eg:
int init_listen(){//初始化监听描述符
int listen_fd;
int ret;
struct sockaddr_in server_addr;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
fprintf(stderr, "fail to socket : %sn", strerror(errno));
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ret = bind(listen_fd, (struct sockaddr *) &server_addr, sizeof(server_addr));
if (ret < 0) {
perror("fail to bind");
return -1;
}
listen(listen_fd, 5);
return listen_fd;
}
服务器如何获得三次握手的新描述符?
int listen_handler(int listen_fd) {
int new_fd;
new_fd = accept(listen_fd, NULL, NULL);
if (new_fd < 0) {
perror("fail to accept");
return -1;
}
return new_fd;
}
循环读取文件,发送文件的方法?
void read_func() {
char buf[1024];
fd = open(xxxx);
ret = read(fd, buf, sizeof(buf));
while (ret) {
if (ret < 0) {
xxxx;break;
}
send(xxx, buf, ret);
ret = read(fd, buf, sizeof(buf));
}
}
大小端的检查方法
void judge()
{
int i = 48,*p = &i;
char c = *((char*)p);
puts(c=='0'?"小端":"大端");
}