一、什么是Socket
套接字是一种网络API,用来开发网络程序。在Unix中,一切皆文件,可以打开、关闭以及读写。Socket也是一种文件,它所提供的一些函数就是在进行打开、关闭和读写。Socket位于应用层和传输层之间,程序员不需要过多的了解复杂的传输层协议,仅需要使用socket提供的接口就可以实现数据通信。
二、Socket的类型
Linux支持多种套接字类型,即应用程序希望的通信服务类型。
- SOCKET_STREAM:双向可靠数据流,对应TCP。
- SOCKET_DGRAM :双向不可靠数据报,对应UDP
- SOCKET_RAW:是低于传输层的低级协议或物理网络提供的套接字类型,可以访问内部网络接口。例如接收和发送ICMP报文
具体选用那种类型根据实际开发需求而定。
三、Socket的地址结构
1.通用地址结构
由于套接字函数需接收来自不同协议的地址结构,ANSI的办法是使用通用的指针类型,即(void *)。套接字函数方法是定义一个通用的套接字地址结构。<sys/socket.h>
struct sockaddr{
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
其中,sa_family是地址家族它的值可以是:AF_INET,AF_INET6和AF_UNIX。
这就要求调用套接字函数时,需将指向特定于协议的地址结构的指针类型转换成指向通用的地址结构的指针。
如struct sockaddr_in serv
bind(sockfd, (struct sockaddr *)&serv, sizeof(serv));
2.IPv4
<netinet/in.h>
typedef uint32_t in_addr_t;
typedef uint16_t in_port_t;
typedef unsigned short sa_family_t;
struct in_addr{
in_addr_t s_addr;
};
struct sockaddr_in {
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
3.IPv6
<netinet/in.h>
typedef uint16_t in_port_t;
typedef unsigned short sa_family_t;
struct in6_addr{
uint8_t s6_addr[16];
};
struct sockaddr_in6{
uint8_t sin6_len;
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
};
四、Socket基本函数
1.Socket函数
#include <sys/socket.h>
int socket(int family, int type, int protocol)
返回:非负套接字(sockfd)为成功;-1为出错。family:通信域;type:套接字类型;
Protocol:指明此socket请求所使用的协议,一般采用默认的0,表示按给定的域和套接字类型选择默认协议。也可以使用如下相关符号常数来表示:IPPROTO_TCP:表示TCP协议,IPPROTO_UDP:表示UDP协议。
2. Bind函数
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_len len)
返回:0为成功;-1为出错并置errno
该函数指明套接字将使用本地的哪一个协议端口进行数据传送(IP地址和端口号),注意:协议地址addr是通用地址。
Len是该地址结构(第二个参数)的长度。
一般而言,服务器调用此函数,而客户则很少调用它。
3.Listen函数
#include <sys/socket.h>
int listen(int sockfd, int backlog)
返回:0为成功;-1为出错并置errno
函数listen仅被服务器调用,它完成两件事情:
(1)函数listen将未连接的套接字转化成被动套接字,指示内核应接受指向此套接字的连接请求;
(2)函数的第二个参数规定了内核为此套接字排队的最大连接个数,但与系统所允许的最大连接数没有关系。
对于给定的监听套接字,内核要维护两个队列,即未完成连接队列和已完成连接队列,但还没有被应用程序所接受,两个队列之和不超过backlog;
4.Connect函数
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回:0为成功;-1为出错
5.Accept函数
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
返回:非负描述字(connfd)为OK;-1为出错;
accept函数由TCP服务器调用;从已完成连接队列头返回下一个已完成连接;如果该队列空,则进程进入睡眠状态。
函数返回的套接字为已连接套接字,应与监听套接字区分开来。
该函数最多返回三个值:一个既可能是新套接字也可能是错误指示的整数,一个客户进程的协议地址(由cliaddr所指),以及该地址的大小;也就是说,服务器可以通过参数cliaddr来得到请求连接并获得成功的客户的地址和端口号
6.Close函数
#include <unistd.h>
int close(int sockfd);
返回:0为成功;-1为出错
7.Read和write函数
#include <unistd.h>
int read(int fd, char *buf, int bufflen);
int write(int fd, char *buf, int len);
返回:大于0为字节大小;-1为出错;
8.Send函数(TCP)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send (int fd, const void *msg, size_t len, int flags);
返回:非0为发送成功的数据长度;-1为出错;
flags 是传输控制标志,其值定义如下:
0:常规操作,如同write()函数
MSG_OOB,发送带外数据(TCP紧急数据)。
MSG_DONTROUTE:忽略底层协议的路由设置,只能将数据发送给与发送机处在同一个网络中的机器上。
9.Recv函数(TCP)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int fd, void *buf ,size_t len, int flags);
返回:大于0表示成功接收的数据长度;0: 对方已关闭,-1:出错。
flags是传输控制标志,其值定义如下:
0:常规操作,如同read()函数;
MSG_PEEK:只查看数据而不读出数据,后续读操作仍然能读出所查看的该数据;
MSG_OOB:忽略常规数据,而只读带外数据;
MSG_WAITALL:等待直到所有的数据可用,仅SOCK_STREAM。此时len表示的是等待数据的长度,而不再是buf的长度
10.Sendto函数(UDP)
#include <sys/types.h>
#include <sys/socket.h>
int sendto (int s, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);
第一个参数为socket描述符;第二个参数为数据缓存地址;第三个参数为长度;第四个参数一般为0;第五个参数为对方地址;第六个参数为对方地址长度。
11.Recvfrom函数(UDP)
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen);
详解和sendto函数类似。
五、示例
TCP示例:https://blog.csdn.net/qq_40077103/article/details/82561043
UDP示例:https://blog.csdn.net/qq_40077103/article/details/82561204