1.简述
1.网络中标识主机和地址:ip地址唯一标识主机,协议+端口唯一标识主机的进程
2.三元组(ip地址、协议、端口)唯一标识网络中的进程,网络中的进程通过socket实现。
3.socket是一种文件:对其打开->读/写->关闭
2.socket的函数
1.socket();
int socket(int domain,int type,int protocol);//对应于普通文件的打开操作,普通文件返回一个文件的描述符,socket()返回一个socket的描述符,唯一标识一个socket,后续的读写操作都把它作为参数传进去
参数:
- domain:协议簇,常用的协议族有,AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
- type:常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
- protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
注意:type不能和protocol任意组合,如SOCK_STREAM不能和IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
2.bind();
int bind(int sockfd,const struct *addr,socklen_t addrlen);
参数:
- sockfd:socket描述符,通过socket()创建
- addr:一个结构体指针,指明绑定给socket的协议地址,协议簇不同内容不同,比如ipv4对应的是:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
//ipv6对应的是:
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
};
struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address */
};
//Unix域对应的是:
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
- addrlen:对应的是地址的长度。
注意:服务端需要bind,客户端不需要。服务端在listen()之前bind(),客户端在connect()之前不需要bind(),系统自动给客户端分配一个。
3.listen()、connect()
服务器:调用socket()、bind()之后就会调用listen()来监听socket
客户端:调用connect()发出连接请求
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
listen函数的第一个参数即为要监听的socket描述字,第二个参数为相应socket可以排队的最大连接个数,如果超过这个最大连接个数,就拒绝连接。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
connect()第一个参数是客户端的socket描述字,第二参数是服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。
4.accept()
服务端:socket()->bind()->listen()->accept()
客户端:socket()->connect()
int accepr(int sockfd,struct sockaddr* addr,sockelen_t *addlen)
- sockfd:服务端的套结字
- addr:客户端的协议地址
- addrlen:协议长度
如果accept成功,其返回中是一个套结字,代表和客户端的TCP连接,当服务器完成客户的服务,关闭该套结字。
服务器还可以使用I/O多路复用的方式,来等待一个请求的到来。
5.read()、write()
网络I/O的操作
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvform()/sendto()
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// send成功返回代表把数据无错误地发送到了网络驱动程序上
// 对于支持报文边界的协议,如果尝试发送单个报文的长度超过协议所支持的最大长度,send就会失败,errno设为EMSGSIZE
// 对于字节流协议,send会阻塞直到整个数据传输完成。send和sendto很类似,区别是sendto可以在无连接的套接字上指定一个目标地址。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// recv和read类似,但是recv可以指定如何接受数据,flags标志位可以指定(MSG_DONTWAIT,非阻塞模式);(MSG_PEEK,返回数据包内容,而不真正取走数据包)...
// 对于SOCK_STREAM套接字,接受的数据可以比预期的少。MSG_WAITALL标志会阻止这种行为,直到所请求的数据全部返回,recv函数才会返回。
// 如果发送者调用shutdown来结束传输,或者网络协议支持按默认的顺序关闭并且发送端已经关闭,那么当所有的数据接受完毕后,recv会返回0
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
// 对于无连接的套接字,需要先连接,在用send发送。sendto函数中有目的地址,可以发送无连接的报文。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
// 如果有兴趣定位发送者,可以使用recvfrom来得到数据发送者的源地址
// recvfrom通常用于无连接的套接字,否则recvfrom等同于recv
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags)
// 带有msghdr结构,和writev(聚集写)类似,将多个缓冲区写到通道中。
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
// 将数据送入多个缓冲区,类似于readv
6.close();
close(int fd);
#include <unistd.h>
int close(int fd);
库文件
#include<netinet/in.h>
IPPROTO_TCP
sockaddr_in
INADDR_ANY
htonl
htons
#include<sys/types.h>
#include<sys/socket.h>
3.专有名词
带外数据:TCP称其为紧急数据,是一些通信协议栈所支持的可选功能,具有高优先级传输。TCP支持带外数据,但是UDP不支持。紧急数据被接受时,会发送SIGURG信号。
进程的基本属性:进程ID,父进程ID,进程组ID,会话和控制终端
- 进程ID:每一个进程都有一个非负整数表示的唯一进程ID(PID),进程ID(PID)是无法在用户层改动的。在linux系统中,PID为0的进程一般是调度进程,经常被成为交换进程。
idle进程:由系统自动创建的,运行在内核态,完成加载系统后,演变为进程调度、交换。
init进程:是由idle进程通过kernel_thread创建,完成系统的初始化,是系统中所有其他用户进程的祖先进程。linux中所有的进程都是由init进程创建的并运行的。首先在linxu内核中启动,然后在用户空间中启动init进程,在启动其他系统进程,在系统启动完成后,init变为守护进程监视系统其他进程。
kthread进程:由idle进程通过kernel_thread进创建,并始终运行在内核空间,负责所有内核线程的调度和管理。
**守护进程:**守护进程是脱离中断,并在后台运行的进程,一般守护进程的生命周期是系统开始到系统结束。守护进程的创建步骤:①创建子进程,父进程退出(孤儿进程一般是由1号进程收养);②创建会话setsid();;③改变当前目录为根目录chdir("/");;④设置文件掩码umask(0);
文件权限 rwx 421,可读,可写,可执行 - 父进程ID:除了0号进程,其他进程都有一个进程创建,这个进程就叫做父进程,这个ID就是父进程ID,叫做PPID。
- 进程组ID:是一个进程或多个进程的集合。 进程组的作用在于方便对进程的管理,如果一个任务需要创建100个进程,在终止的时候,如果没有进程组,需要一个个的去终止。有了进程组,就可以将这100个进程设为一个进程组,他们共有一个组号,并且选取辈分最高的那个进程作为组长,组长的进程ID就是这个进程组的ID。那么就可以通过杀死整个进程组,来关闭这100个进程并且是严格有序的。
setpgid(pid_t pidm,,pid_t pgid);//修改进程组ID的接口
- 会话:会话是一个或多个进程组的集合,包含了登录用户的所有活动。
- 一次会话就是打开一个终端,用户的登录shell就是会话的首进程。一个会话可以包含多个进程组,但是只能有一个前台进程组,每个进程都有一个会话首领(leader),即创建会话的进程。当前进程不是进程组组长时,可以调用setsid创建一个新的会话,这个进程就成为新会话的leader。
- 前台进程和后台进程: 1.对于执行很长时间的进程,不用傻傻的等待命令运行完毕才执行下一个命令,可以在命令的结尾添加&符号,将命令放入后台执行,这样,该命令对应的进程组就是后台进程组。2.在任意时刻,shell只能由一个前台进程组,只有前台进程组才会读取终端的输入,在终端信号输入终端字符,对应的信号指挥发送给前台进程组。
- 为什么会话的创建者不能是进程组的组长: 如果进程组组长创建会话,这个进程就会迁移到新的会话中,进程组的其他成员仍然在老会话中,就会出现一个进程组的进程分属不同的会话中,这就破坏了进程组和会话的严格的层次关系。
- 作业: shell分前后台控制的不是进程,而是作业或者进程组,作业可以由多个进程组组成。shell可以运行一个前台作业和多个后台作业,这成为作业控制。作业和进程组的区别:如果作业中某个进程又创建了子进程,那么这个子进程不属于作业。
程序和进程的区别: 程序是存储在磁盘上的可执行机器指令和数据的静态实体。进程是被操作系统从磁盘加载到内存上的、动态的、可运行的指令与数据的集合,实在运行的动态实体。
临界区资源: 各进程采用互斥的方式,访问的资源叫做临界区资源。