一、套接字
1、套接字地址结构
1.struct sockaddr
结构struct sockaddr定义了一种通用的套接字地址,它在linux/socket.h中的定义代码如下:
struct sockaddr {
unsigned short sa_family; //地址类型
char sa_data[14]; //14字节的协议地址
};
sa_family:套接字的协议族类型
sa_data:存储具体的协议地址
一般在编程中并不对该结构体进行操作,而是使用另一个与它等价的数据结构:sockaddr_in
2.struct sockaddr_in
每种协议族都有自己的协议地址格式,TCP/IP协议族的地址格式为结构体struct sockaddr_in,它在netinet/in.h头文件中定义,格式如下:
struct sockaddr_in {
unsigned short sin_family; //地址类型
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //IP地址
unsigned char sin_zreo[8]; //填充字节,一般赋值为0
};
sin_family:表示地址类型,对于使用TCP/IP协议进行的网络编程,该值只能是AF_INET
sin_addr:用来存储32位的IP地址
struct in_addr的定义如下:
struct in_addr {
unsigned long s_addr;
};
结构体sockaddr的长度为16字节,结构体sockaddr_in的长度也为16字节。通常在编写基于TCP/IP协议的网络程序时,使用结构体sockaddr_in来设置地址,然后通过强制类型转换成sockaddr类型
2、创建套接字
socket函数
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
domain:指定创建套接字所使用的协议族,它们在头文件linux/socket.h中定义。常用的协议族如下:
- AF_UNIX:创建只在本机内进行通信的套接字
- AF_INET:使用IPv4TCP/IP协议
- 使用IPv6TCP/IP协议
type:指定套接字的类型
- SOCK_STREAM:创建TCP流套接字
- SOCK_DGRAM:创建UDP数据报套接字
- SOCK_RAW:创建原始套接字
procotol:通常设置为0,表示通过参数domain指定的协议族和参数type指定的套接字类型来确定使用的协议。当创建原始套接字时,系统无法惟一地确定协议,此时就需要使用该参数指定所使用的协议。
执行成功返回一个新创建的套接字;若有错误发生则返回-1,错误代码存入errno中。
3、建立连接
connect函数
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
sockfd:是一个由socket创建的套接字。如果该套接字类型是SOCK_STREAM,则connect函数向服务器发出连接请求。如果套接字的类型是SOCK_DGRAM,则connect函数并不建立真正的连接,它只是告诉内核与该套接字进行通信的目的地值,只有该目的地址发来的数据才会被该socket接受。
serve_addr:服务器的IP地址和端口号。
- 通常一个面向连接的套接字(如TCP套接字)只能调用一次connect函数。而对于无连接的套接字(如UDP套接字)则可多次调用connect函数以改变与目的地址的绑定。将参数serv_addr中的sa_family设置为AF_UNSPEC可以取消绑定。
addrlen:参数serv_addr的长度。
执行成功返回0,有错误发生则返回-1,错误代码存入errno中。
4、绑定套接字
bind函数
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
sockfd:是一个由socket创建的套接字。
my_addr:制定了sockfd将绑定到的本地地址,可以将my_addr的sin_addr设置为INADDR_ANY而不是某个确定的IP地址就可以绑定到任何网络接口。
函数执行成功返回0,当有错误发生时返回-1。
5、在套接字上监听
listen函数
由函数socket创建的套接字是主动套接字,这种套接字可以用来主动请求连接到某个服务器(通过函数connect)。但是作为服务器端的程序,通常在某个端口上监听等待来自客户端的连接请求。
#include<sys/socket.h>
int listen(int s, int backlog);
backlog:指定了该连接队列的最大长度。如果连接队列已经达到最大,之后的连接请求将被服务器拒绝。
执行成功返回0,当有错误发生返回-1。
6、接受连接
accept函数
只能对面向连接的套接字使用accept函数,accept执行成功时,将创建一个新的套接字,并且为这个新的套接字分配一个套接字描述符,并返回这个新的套接字的描述符。这个新的套接字描述符与打开文件时返回的文件描述符类似,进程可以利用这个新的套接字描述符与客户端交换数据,参数s所指定的套接字继续等待客户端的连接请求。
#include<sys/types.h>
#include<sys/socket.h>
int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
s:由函数socket创建,经函数bind绑定到本地某一端口上,然后通过函数listen转化而来的监听的套接字。
addr:用来保存发起连接请求的主机的地址和端口。
addrlen:addr所指向的结构体的大小。
执行成功返回一个新的代表客户端的套接字,出错返回-1。
7、关闭套接字
1、close函数
关闭一个套接字
#include<unistd.h>
int close(int fd);
fd :一个套接字描述符
执行成功返回0,出错返回-1。
2、shutdown函数
shutdown函数与close函数类似,但是shutdown函数功能更强大。它允许对套接字进行单向关闭或全部禁止。
#include<sys/socket.h>
int shutdown(int s, int how);
s:待关闭的套接字描述符
how:指定了关闭方式,具体取值如下
- SHUT_RD:将连接上的读通道关闭,此后进程将不能再接收任何数据,接收缓冲区中还未被读取的数据也将被丢弃,但仍可以在该套接字上发送数据
- SHUT_WR:将连接上的写通道关闭,此后进程将不能再发送任何数据,发送缓冲区中还未被发送的数据也将被丢弃,但仍可以在该套接字上接收数据
- SHUT_RDWD:读写通道都将被关闭
二、TCP套接字的数据传输
1、发送数据
send函数
函数send只能对于处于连接状态的套接字使用。如果要发送的数据长度大于该套接字的缓冲区剩余空间大小时,send()一般会被阻塞,如果该套接字被设置为非阻塞方式,则此时立即返回-1并将errno设为EAGAIN.
#include<sys/types.h>
#include<sys/socket.h>
ssize_t send(int s, const void *msg, size_t len, int flags);
s:已建立好连接的套接字描述符
msg:指向存放待发送数据的缓冲区
len:待发送数据的长度
flags:控制选项
- MSG_OOB:在指定的套接字上发送带外数据
- MSG_DONTROUTE:通过最直接的路径发送数据,而忽略下层协议的路由设置
执行成功返回实际发送数据的字节数,出错则返回-1。
2、接受数据
recv函数
如果一个数据包太长以至于缓冲不能完全放下时,剩余部分的数据将可能被丢弃(根据接受数据的套接字类型而定)。如果在指定的套接字上无数据到达时,recv()将被阻塞,如果该套接字被设置为非阻塞方式,则立即返回-1。函数recv接收到数据就返回,并不会等待接收到参数len指定长度的数据才返回。
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int s, void *buf, size_t len, int flags);
s:套接字描述符
buf:指定的缓冲区
len:缓冲区长度
flags:控制选项一般设置为0或取以下数值
- MSG_OOB:请求接受带外数据
- MSG_PEEK:只查看数据而不读出
- MSG_WAITALL:只在接收缓冲区满时才返回
执行成功返回收到的数据字节数,出错则返回-1。
三、UDP套接字的数据传输
1、发送数据
sendto函数
sendto的功能与send类似,但函数sendto不需要套接字处于连接状态,所以该函数通常用来发送UDP数据,并且需要指定数据的目的地址
#include<sys/types.h>
#include<sys/socket.h>
ssize_t sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, sockle_t tolen);
to:目的地址
tolen:目的地址的长度
执行成功返回实际发送数据的字节数,出错则返回-1。
2、接受数据
recvfrom函数
recvform函数和recv函数功能类似,只是函数recv只能用于面向连接的套接字,而函数recvform没有此限制,可以用于从无连接的套接字(如UDP套接字)上接收数据。
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recvfrom(int s,void *buf,size_t len,int flags, struct sockaddr *from, socklen_t *fromlen)
buf:指向接收缓冲区
len:缓冲区的大小
flags:控制选项,与recv一致
from:如果参数from非空,且该套接字不是面向连接的,则函数recvfrom返回时,参数from中将保存数据的源地址
fromlen:参数fromlen在调用recvfrom前为参数from的长度,调用recvfrom后将保存from的实际大小。
执行成功返回实际接收到数据的字节数,出错则返回-1。