1.套接字
套接字:英文为socekt,是一个指向传输提供者的句柄。
套接字分为:原始套接字、流式套接字和数据包套接字三种。
原始套接字:能够给是程序开发人员对底层的网络传输机制进行控制,在原始套机子下接收的数据汇中含有IP头;
流式套接字:提供双向、有序、可靠的数据传输服务,该类型套接字在通信前需要双方建立连接,大家熟悉的TCP协议采用的就是流式套接字;
数据包套接字:与流式套接字对应,提供双向的数据流,但是它不保证数据传输的可靠性、有序性和五重复性,UDP协议采用的就是数据包套接字。
2.套接字的相关操作
套接字建立
建立套接字时调用socket()函数,定义:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
参数说明:
int domain:代表所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族);
int type:指定套接字的类型,SOCK_STREAM(流式套接字)、SOCK_DGRAM(数据包套接字)、SOCK_RAW(原始套接字);
int protocol:通常赋值“0”。
返回值:返回一个整形socket描述符,可在后面的函数调用中使用。
调用socket()函数时,套接字执行体将创建一个套接字,也就是为一个套接字数据结构分配存储空间
通用的套接字数据结构的定义如下:
struct sockaddr
{
unsigned short sa_family; /*地址族,AF_xxx*/
char sa_data[14]; /*14字节的协议地址*/
};
成员变量sa_family一般为AF_INET代表TCP/IP(互联网络)地址族;
成员变量sa_data[14] 包含该套接字的IP地址和端口号。
不同的网络协议使用的结构体不一样,但都以通用结构体”sockaddr”+”“,即socketaddr开头,后面加上不同协议的后缀即可,例如尝试用的IPv4对应的sockaddr_in数据结构如下:
struct sockaddr
{
short int sin_family; /*地址族*/
unsigned short int sin_port; /*端口号*/
strut in_addr sin_addr; /*IP地址*/
unsigned char sin_zero[8]; /*填充0以保持与strcut sickaddr同样大小*/
};
- 套接字配置
面向连接的套接字客户端通过调用connect()函数在套接字数据结构体中保存本地信息和远端信息。无连接套接字的客户端和服务端以及面向连接套接字的服务端通过调用bind()函数来配置本地信息。
bind()函数定义:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr *my_addr,socklen_t addrlen);
参数说名:
sockfd: 调用socket()函数返回的套接字描述符;
my_addr:是一个执行包含有本机IP地址及端口等信息的sockaddr类型的指针;
addrlen:通常被设置为结构体struct sockaddr的长度,即sizeof(struct sockaddr)。
使用bind()函数时可以用下面的赋值,实现自动获取本机IP地址和随机获取一个没有被占用的端口号:
my_addr.sin_port = 0; /*系统随机选择一个未被使用的端口号*/
my_addr.sin_addr.s_addr = INADDR_ANY; /*填入本机IP地址*/
- 字节优先顺序
计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。
在互联网上数据以高位字节优先顺序在网络传输,所以对于内部是以低位字节优先方式存储的机器需要进行数据转换否则会出现数据不一致的情况。
Linux系统下的几个字节顺序的转换函数:
#inclued <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); /*把32位值从主机字节序转换成网络字节序*/
uint16_t htons(uint16_t hostshort); /*把16位值从主机字节序转换成网络字节序*/
uint32_t ntohl(uint32_t netlong); /*把32位值从网络字节序序转换成主机字节*/
uint16_t ntohs(uint16_t netshort); /*把16位值从网络字节序序转换成主机字节*/
- 连接建立
面向连接的客户程序使用connetc()函数来配置套接字并与远端服务器建立一个TCP连接,该函数的定义形式如下:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
参数说明:
sockfd:是socket()函数返回的套接字描述符;
serv_addr:是包换远端主机IP地址和端口号的sockaddr类型的指针;
addrlen:是远端地址结构体的长度,通常是结构体struct sockaddr的长度,即sizeof(struct sockaddr)。
- 监听模式
函数listen()使套接字处于被动的监听模式,并为该套接字建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序对它们进行处理。
#include <sys/socket.h>
int listen(int sockfd,int backlog);
参数说明
sockfd:socket()函数返回的套接字描述符;
backlog:指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待accept()等系统调用的操作。
- 接收请求
accept()函数让服务器接受客户的连接请求。函数定义:
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
参数说明
sockfd:是sockfd()函数返回的套接字描述符;
addr:通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求服务的主机的信息;
addrlen:通常为一个指向值为sizeof(struct sockaddr_in)的整型指针变量。
- 数据传输
send()和recv()函数用于在面向连接的套接字上进行数据传输。
send()函数的定义形式如下:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd,const void *msg,size_t len,int flags);
参数说明
sockfd:是socket()函数返回的套接字描述符;
msg:是一个指向要发送数据的指针;
len:是以字节为单位的数据的长度;
flag:一般情况下设置为0。
send()函数返回实际上发送出的字节数,在程序中应该将send()的返回值与想要发送的字节数进行比较,当send()返回值与len不匹配时,应该对这种情况进行处理。
recv()函数的定义形式如下:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int s,void *buf,size_t len,int flags);
参数说明
s:是socket()函数返回的套接字描述符;
buf:是存放接受数据的缓冲区;
len:是缓冲的长度;
flags:一般情况下设置为0。
函数sendto()和recvfrom()用于在无连接的数据包套接字方式下进行数据传输。由于本地套接字并没有与远端机器建立连接,所以在发送数据是应指明目的地址。
- 结束传输
数据操作结束后,就可以调用close()函数来释放该套接字,从而停止在该套接字上的任何数据操作,该函数的定义形式如下:
#include <unistd.h>
int close(int fd);
参数说明
fd:调用socket()函数时返回的套接字描述符。
除此之外,还可以调用shutdown()函数来关闭该套接字。该函数允许只停止在某个方向上的数据传输,而另一个方向上的数据传输继续进行,其定义形式如下:
#include <sys/socket.h>
int shutdown(int s,int how);
参数说明
s:是需要关闭的套接字的描述符;
how:允许为关闭操作选择以下几种方式。
0:不允许继续接收数据;
1:不允许继续发送数据;
2:不允许继续发送和接收数据。
如果以上几种行为都不允许,那么可以直接调用close()函数。