socket起源于Unix,Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行读/写IO、打开、关闭。根据美国计算机历史博物馆的记载,Croker写道:“一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定。”
网络端口编号从 0 到 65535,其中 0 - 1023 是公认的端口号,就是已经被一些知名的软件给占用了,留给我们程序里面使用的是 1024 - 65535 端口。
socket通过绑定操作占领某个端口,接下来其他程序将不能使用此端口,一旦此端口收到数据,系统都会转发给该程序。
在TCP/IP网络协议中,通信的两个进程间相互作用的主要模式是客户/服务器(Client/Server, C/S)模式,即客户向服务器发出服务请求,服务器接收到请求后,提供相应的服务,其过程如下:
(1)服务器创建一个socket,绑定到指定端口。
(2)开始监听等待客户请求的到来。
(3)客户端创建一个socket,绑定到随意一个可用的端口,然后向服务器的socket发送请求。
(4)接收到客户socket的请求时,为了不妨碍继续监听其它客户请求的工作,为当前客户创建一个专属的连接socket来处理该客户的请求,读取客户发送来的数据,发送应答数据给客户,操作完毕以后结束与该客户的连接,于此同时,监听socket则仍可以继续监听其它客户的请求,形成多线程并发操作。
下面是实现上述过程的Linux socket库函数
创建socket
int socket (int domain, int type, int protocol);
函数原型位于 /usr/include/x86_64-linux-gnu/sys/socket.h
Create a new socket of type in domain, using protocol.
返回值
根据这三个参数建立一个socket,并将相应的资源分配给它,Returns a file descriptor for the new socket, or -1 for errors.
domain
指定通信发生的区域
AF_INET: For communicating between processes on different hosts connected by IPV4
AF_INET6:for processes connected by IPV6
AF_LOCAL: defined in the POSIX standard for communication between processes on the same host.
type
指communication type
(1)SOCK_STREAM: TCP(reliable, connection-oriented) ,数据无差错、无重复发送、按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。文件传送协议(FTP)即使用此。
(2)SOCK_DGRAM:UDP(unreliable, connectionless),数据报以独立包形式被发送,不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。网络文件系统(NFS)使用此。
(3)SOCK_RAW:该接口允许对较低层协议,如IP、ICMP直接访问,常用于检验新的协议实现或访问现有服务中配置的新设备。
The concept of "connections" apply to SOCK_STREAM/TCP type of sockets. Connection means a reliable "stream" of data such that there can be multiple such streams each having communication of its own. Think of this as a pipe which is not interfered by other data.
Other sockets like UDP , ICMP , ARP don't have a concept of "connection". These are non-connection based communication. Which means you keep sending or receiving packets from anybody.
protocol
说明socket使用的特定协议,默认为0,This is the same number that appears on the protocol field in the IP header of a packet.
If protocol is zero, one is chosen automatically.
设置socket的属性
int setsockopt (int fd, int level, int optname, const void *optval, socklen_t optlen);
函数原型位于 /usr/include/x86_64-linux-gnu/sys/socket.h
Set socket FD's option OPTNAME at protocol level LEVEL to *OPTVAL (which is OPTLEN bytes long). This helps in manipulating options for the socket. This is completely optional.
常用功能有:
1. reuse of address and port. Prevents error such as: “address already in use”.
2. 设置接收和发送超时的时长。
返回值
Returns 0 on success, -1 for errors.
指定socket监听的地址和端口
int bind (int fd, CONST_SOCKADDR_ARG addr, socklen_t len);
函数原型位于 /usr/include/x86_64-linux-gnu/sys/socket.h
Give the socket FD the local address ADDR (which is LEN bytes long),包括本地地址和本地端口。
返回值
如果没有错误发生,bind()返回0,否则返回-1
fd
被指定的socket
addr
本地地址,包括ip和port,INADDR_ANY 表示监听所有IP地址。
len
指定addr的长度。
socket开始监听连接请求
int listen (int fd, int n);
函数原型位于 /usr/include/x86_64-linux-gnu/sys/socket.h
Prepare to accept connections on socket FD. N connection requests will be queued before further requests are refused.
返回值
Returns 0 on success, -1 for errors.
fd
监听socket
n
请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为5。
If a connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED.
socket发起连接
int connect (int fd, __CONST_SOCKADDR_ARG addr, socklen_t len);
函数原型位于 /usr/include/x86_64-linux-gnu/sys/socket.h
Open a connection on socket FD to peer at ADDR (which LEN bytes long).
For connectionless socket types, just set the default address to send to and the only address from which to accept transmissions.
返回值
Return 0 on success, -1 for errors.
fd
发起连接的socket
addr
连接目标socket的地址,包括ip和port.
len
addr的长度.
等待和接收连接请求
int accept (int fd, __SOCKADDR_ARG addr, socklen_t *__restrict addr_len);
函数原型位于 /usr/include/x86_64-linux-gnu/sys/socket.h
Await a connection on socket FD. When a connection arrives, open a new socket to communicate with it, set *ADDR (which is *ADDR_LEN bytes long) to the address of the connecting peer and *ADDR_LEN to the address's actual length.
It extracts the first connection request on the queue of pending connections for the listening socket, creates a new connected socket, and returns a new file descriptor referring to that socket. At this point, the connection is established between client and server, and they are ready to transfer data.
返回值
return the new socket's descriptor, or -1 for errors.
fd
监听socket。
addr
用来接收客户socket的地址, addr的确切格式由socket创建时建立的地址族决定。
len
addr的实际字节数
socket发送数据
ssize_t send (int fd, const void *buf, size_t n, int flags);
函数原型位于 /usr/include/x86_64-linux-gnu/sys/socket.h
返回值
如果没有错误发生,send()返回总共发送的字节数; 否则它返回-1
fd 发送数据的socket。
buf 数据缓冲区的指针
n buf的字节数
flags 指定传输控制方式,如是否接收带外数据等。
socket接收数据
ssize_t recv (int fd, void *buf, size_t n, int flags);
函数原型位于 /usr/include/x86_64-linux-gnu/sys/socket.h
Read N bytes into BUF from socket FD.
返回值
Returns the number read or -1 for errors. 如果连接被关闭,返回0
fd 接收数据的socket。
buf 接收数据缓冲区的指针
n 接收数据的最大字节数
flags 指定传输控制方式,如是否接收带外数据等。
关闭socket
int close (int fd);
函数原型位于 /usr/include/unistd.h
Close the file descriptor FD
通讯的双方都可以关闭当前的连接。
设置socket接收和发送超时的时长
一个新创建的socket默认是阻塞的,默认的socket发送和接收的超时时长为0,表明永不超时,意味着recv/send会一直等待对方发送和接收数据,这显然是不行的,要设置socket发送和接收超时的时长。
struct timeval {
__kernel_old_time_t tv_sec; /* seconds */
__kernel_suseconds_t tv_usec; /* microseconds */
};
struct timeval的定义在 /usr/include/linux/time.h
1. 获取接收的超时值
socklen_t optlen = sizeof(struct timeval);
struct timeval tv;
getsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, &optlen);
2. 设置接收的超时值
socklen_t optlen = sizeof(struct timeval);
struct timeval tv; tv.tv_sec = 10; tv.tv_usec = 0; // 10秒后超时
setsockopt(socket_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, optlen);
检查getsockopt和setsockopt函数的返回值,0表示成功,-1表示失败。
设置发送超时与接收超时类似,只需将SO_RCVTIMEO改为SO_SNDTIMEO
socket常用的数据结构
// 定义于 /usr/include/linux/in.h
/* Internet address. */
struct in_addr {
__be32 s_addr; // long int format of IP address
};
/* Structure describing an Internet (IP) socket address. */
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
/* Pad to size of `struct sockaddr'. */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)];
};
/* Structure describing a generic socket address. */
struct sockaddr {
__SOCKADDR_COMMON (sa_); /* Common data: address family and length. */
char sa_data[14]; /* Address data. */
};
/* Description of data base entry for a single host. */
struct hostent
{
char *h_name; /* Official name of host. */
char **h_aliases; /* Alias list. */
int h_addrtype; /* Host address type. */
int h_length; /* Length of address. */
char **h_addr_list; /* List of addresses from name server. */
#ifdef __USE_MISC
# define h_addr h_addr_list[0] /* Address, for backward compatibility.*/
#endif
};
操作地址的函数
/* 定义于/usr/include/arpa/inet.h
Convert Internet host address from numbers-and-dots notation in CP
into binary data in network byte order.
eg: in_addr.s_addr = inet_addr("74.125.235.20");*/
in_addr_t inet_addr (const char *cp);
本文参考资料
ubuntu linux 24.04
https://blog.csdn.net/qiuchangyong/article/details/82757819
https://www.binarytides.com/socket-programming-c-linux-tutorial/#google_vignette
https://www.geeksforgeeks.org/socket-programming-cc/
https://www.cnblogs.com/bandaoyu/p/16752853.html
以上如有错漏之处,敬请大家指正。我的联系方式:
微信:TobeBuda
Email/Paypal: jinmin.si@outlook.com
邀请您加入「社区资讯服务」创业微信群,共同探讨打造社区资讯服务的美好未来。
2024年6月16日