概念
Socket中文意思是“插座”.在Linux环境下.用于表示进程x间网络通信的特殊文件类型.本质为内核借助缓冲区形成的伪文件.
既然是文件.那么理所当然的.我们可以使用文件描述符引用套接字.Linux系统将其封装成文件的目的是为了统一接口.使得读写套接字和读写文件的操作一致.区别是文件主要应用于本地持久化数据的读写.而套接字多应用于网络进程间数据的传递.
在TCP/IP协议中.“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程.“IP地址+端口号”就对应一个socket.欲建立连接的两个进程各自有一个socket来标识.那么这两个socket组成的socket pair就唯一标识一个连接.因此可以用Socket来描述网络连接的一对一关系.
套接字通信原理如下图所示:
在网络通信中.套接字一定是成对出现的.一端的发送缓冲区对应对端的接收缓冲区.我们使用同一个文件描述符索发送缓冲区和接收缓冲区.
socket通信创建流程图:
Socket基础API
socket
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:
AF_INET 这是大多数用来产生socket的协议.使用TCP或UDP来传输.用IPv4的地址
AF_INET6 与上面类似.不过是来用IPv6的地址
AF_UNIX 本地协议.使用在Unix和Linux系统上.一般都是当客户端和服务器在同一台及其上的时候使用.
type:
SOCK_STREAM 这个协议是按照顺序的.可靠的.数据完整的基于字节流的连接.这是一个使用最多的socket类型.这个socket是使用TCP来进行传输.
SOCK_DGRAM 这个协议是无连接的.固定长度的传输调用.该协议是不可靠的.使用UDP来进行它的连接.
SOCK_SEQPACKET该协议是双线路的.可靠的连接.发送固定长度的数据包进行传输.必须把这个包完整的接受才能进行读取.
SOCK_RAW socket类型提供单一的网络访问.这个socket类型使用ICMP公共协议.(ping.traceroute使用该协议)
SOCK_RDM 这个类型是很少使用的.在大部分的操作系统上没有实现.它是提供给数据链路层使用.不保证数据包的顺序.
protocol:
0 表示使用默认协议.
返回值:
成功:返回指向新创建的socket的文件描述符.失败:返回-1.设置errno
socket()打开一个网络通讯端口.如果成功的话.就像open()一样返回一个文件描述符.应用程序可以像读写文件一样用read/write在网络上收发数据.如果socket()调用出错则返回-1.对于IPv4.domain参数指定为AF_INET.对于TCP协议.type参数指定为SOCK_STREAM.表示面向流的传输协议.如果是UDP协议.则type参数指定为SOCK_DGRAM.表示面向数据报的传输协议.protocol指定为0即可.
bind
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:
socket文件描述符
addr:
构造出IP地址加端口号
addrlen:
sizeof(addr)长度
返回值:
成功返回0.失败返回-1.设置errno
服务器程序所监听的网络地址和端口号通常是固定不变的.客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接.因此服务器需要调用bind绑定一个固定的网络地址和端口号.bind()的作用是将参数sockfd和addr绑定在一起.使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号.
listen
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd:
socket文件描述符
backlog:
在Linux 系统中.它是指排队等待建立3次握手队列长度
/*
查看系统默认backlog:
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
改变 系统限制的backlog 大小:
vim /etc/sysctl.conf
最后添加
net.core.somaxconn = 1024
net.ipv4.tcp_max_syn_backlog = 1024
保存.然后执行
sysctl -p
*/
listen()声明sockfd处于监听状态.并且最多允许有backlog个客户端处于连接待状态.如果接收到更多的连接请求就忽略.listen()成功返回0.失败返回-1.
accept
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockdf:
socket文件描述符
addr:
传出参数.返回链接客户端地址信息.含IP地址和端口号
addrlen:
传入传出参数(值–结果).传入sizeof(addr)大小.函数返回时返回真正接收到地址结构体的大小
返回值:
成功返回一个新的socket文件描述符.用于和客户端通信.失败返回-1.设置errno
三次握手完成后.服务器调用accept()接受连接.如果服务器调用accept()时还没有客户端的连接请求.就阻塞等待直到有客户端连接上来.addr是一个传出数.accept()返回时传出客户端的地址和端口号.addrlen参数是一个传入传出参数.传入的是调用者提供的缓冲区addr的长度以避免缓冲区溢出问题.传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区).如果给addr参数传NULL.表示不关心客户端的地址.
connect
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:
socket文件描述符
addr:
传入参数.指定服务器端地址信息.含IP地址和端口号
addrlen:
传入参数.传入sizeof(addr)大小
返回值:
成功返回0.失败返回-1.设置errno
客户端需要调用connect()连接服务器.connect和bind的参数形式一致.区别在于bind的参数是自己的地址.而connect的参数是对方的地址.
在网络编程中.服务器与客户端建立连接的步骤一般为:
- 客户端:socket->connect->write/read->close
- 服务器端:socket->bind->listen->accept->write/read->close