初始socket
1.什么是socket
socket 的原意是“插座”,是一种操作系统提供的进程间通信机制。
在UNIX/Linux系统中,一切皆文件,网络连接也是一个文件,它也有文件描述符。
我们可以通过 socket()
函数来创建一个网络连接,或者说打开一个网络文件,socket()
的返回值就是文件描述符。有了文件描述符,我们就可以使用普通的文件操作函数来传输数据了,例如:
- 用
read()
读取从远程计算机传来的数据; - 用
write()
向远程计算机写入数据。
还有一个比较重要的就是网络字节序。
网络字节序一般都是大端表示的,而主机字节序一般是小端表示的,所以需要包含一段这两者之间的转换函数。
1 |
|
而对于IP地址,我们一般见到的表示形式都是点分十进制,把这种点分十进制转换为网络字节序需要函数inet_pton()
,inet_ntop
将大端的整形数, 转换为小端的点分十进制的IP地址。
2.TCP通信流程
TCP是一个面向连接的,安全的,流式传输协议,这个协议是一个传输层协议。
2.1服务器端通信流程
一般而言,服务器需要一个监听套接字和多个通信套接字,监听套接字负责与客户端建立连接,通信套接字则是与客户端进行数据传送。
- 调用
socket()
函数创建用于监听的套接字,得到一个文件描述符。 - 将得到的监听文件描述符和本地的IP端口进行绑定,
bind()
函数。 listen()
函数设置监听。accept()
函数等待客户端的连接请求,会返回一个用于与客户端通信的文件描述符,如果没有请求就会堵塞。read()
或recv()
接受数据,write()
或send()
发送数据。close()
断开连接,关闭套接字。
- 注意:服务端需要绑定一个特定的端口,不能随意分配。这是因为客户端在请求与服务器端进行连接的时候,需要指定IP地址以及端口,所以服务器的端口对于客户端来说是已知的。
例如,当我们输入网址https://www.bilibili.com
,其实https的默认端口就是443,所以相当于我们省略了端口,输入https://www.bilibili.com:443
也是一样的。再讲一句题外话,我们按下F12,点击网络,也会得到远程服务器的IP地址和端口,发现端口也是443,前提是你要关闭vpn代理。
2.2 客户端的通信流程
一般来说客户端通信的文件描述符只有一个,不需要坚挺的文件描述符。
socket()
函数创建一个套接字,返回文件描述符。connect()
函数连接服务器,需要知道服务器绑定的IP和端口。read()
或recv()
函数接受数据,write()
或send()
发送数据。close()
断开连接。
3.基本API
int socket(int domain, int type, int protocol)
:
参数:domain
地址族,常见的有AF_INET(IPv4)和AF_INET6(IPv6)。type
套接字类型,常见的有SOCK_STREAM(用于TCP协议)和SOCK_DGRAM(用于UDP协议)。protocal
协议,通常置为0,根据前两个参数自动选择合适的协议。
返回值是一个文件描述符。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:sockfd
需要绑定的套接字文件描述符。addr
:存放了服务端用于通信的地址和端口。addrlen
:结构体addr的大小。
sockaddr_in
结构体:
1 |
|
int listen(int sockfd, int backlog)
参数:sockfd
: 文件描述符, 可以通过调用socket()
得到,在监听之前必须要绑定bind()
backlog
: 同时能处理的最大连接要求,最大值为128int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:sockfd
: 监听的文件描述符addr
: 传出参数, 里边存储了建立连接的客户端的地址信息,如客户端的端口和IP。addrlen
: 传入传出参数,用于存储addr指向的内存大小
这个函数是一个阻塞函数,当没有新的客户端连接请求的时候,该函数阻塞;当检测到有新的客户端连接请求时,阻塞解除,新连接就建立了,得到的返回值也是一个文件描述符,基于这个文件描述符就可以和客户端通信了。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:sockfd
: 通信的文件描述符, 通过调用socket()函数就得到了addr
: 存储了要连接的服务器端的地址信息: IP 和 端口,这个IP和端口也需要转换为大端然后再赋值addrlen
:addr
指针指向的内存的大小sizeof(struct sockaddr)
至于发送接受数据的函数,这个是内核来维护的,内核决定多久发送出去就多久发送出去,多久从缓冲区获取数据就多久获取。同时,如果缓冲区满了,write
或send
函数就会被阻塞。
4.示例代码
在我们的示例代码中,服务器端我们要使用多线程,每当一个客户端发过来一个请求时,服务器端都需要创建一个线程去处理这个请求。
1 |
|
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
这行代码的意思是可以绑定本地的任何一个IP地址,服务器端自动地读网卡的实际IP,并与这个实际IP进行绑定。
现在我们还没有客户端的代码,但我们仍然可以对服务器代码进行测试。
首先编译并运行服务器端代码,然后另外开几个终端,输入nc 127.0.0.1 9527
命令。
Linux中的nc命令是一个功能强大的网络工具,也被称为netcat,可以实现TCP/UDP端口的侦听,127.0.0.1代表本地主机,9527端口是服务器监听套接字绑定了的。