1、套接字
socket是最通用的网络通信应用程序的开发接口。linux的网络编程就是套接字编程。
套接字编程的头文件:
#include <sys/types.h>
#include <socket.h>
#include <netinet/in.h>
2、套接字地址结构
1)套接字地址结构:
struct sockaddr{ //不用此结构体编程
unsigned short sa_family;//地址类型,AF_XXX
char sa_data[14];//14字节地址协议
}
sa_family表示套接字协议族类型,对应的TCP/IP协议,该值为AF_INET。
sa_data存储具体的协议地址,被定义成14字节是因为有的协议族使用较长的地址格式。
*一般在编程中并不对该结构体进行操作,而是使用一个与它等价的数据结构:
struct sockaddr_in{
unsigned longsin_family;//地址类型 TCP/IP编程只能是AF_INET
unsigned short intsin_port;//端口号
struct in_addrsin_addr;//IP地址. 32位的IP地址
unsigned charsin_zero[8];//填充字节,一般赋值为0
};
注:
struct in_addr{
unsigned longs_addr;
};
2)设置地址信息代码:TCP/IP编程
struct sockaddr_in sock;
sock.sin_family = AF_INET;
sock.sin_port = htons(80);
sock.sin_addr.s_addr = inet_addr("211.203.13.132");
memset(sock.sin_zero, 0, sizeof(sock.sin_zero));
3、创建套接字
socket函数用来创建套接字:
函数原型:int socket(int domain, int type, int protocol);
domain:创建套接字所使用的协议族;
type:套接字类型;
protocol:常设置为0,表示通过参数domain指定的协议族和type的类型来确定使用的协议。当创建原始套接字时,系统无法唯一确定协议,此时需要用该参数来指定使用的协议。
返回值:成功返回一个新创建的套接字,否则返回-1.
常用的协议族:AF_UNIX,创建只在本机内的套接字;AF_INET,IPv4 TCP/IP协议;AF_INET6,IPv6协议;SOCK_STREAM,创建TCP套接字;SOCK_DGRAM,创建UDP数据报套接字;SOCK_RAM,创建原始套接字.
函数用法示例:
//创建一个TCP套接字
int sock_fd; // 4中有应用
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
4、在套接字上创建连接
函数connect用来在一个指定的套接字上创建一个连接。connect函数用于向服务器发出连接请求,服务器的IP地址和端口号由参数serv_addr指定。
函数原型:int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
sockfd:是一个由函数socket()创建的套接字。
serv_addr:是一个地址结构。
addrlen:为参数serv_addr的长度。
函数用法示例:
struct sockaddr_in serv_addr;
...
/*inet_aton()函数将一个字符串转换成一个网络地址,并把该网络地址赋值给第二个参数*/
if(inet_aton("163.125.252.12", &serv_addr.sin_addr) < 0)
{错误提示}
if(connect(sock_fd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)) < 0)//sock_fd在上面有定义
{错误提示}
5、绑定套接字(服务器端)
函数bind将一个套接字与某个端口绑定在一起(服务器端应用的函数)。在客户机/服务器模型中,服务器端的IP地址和端口号一般是固定的,在服务器的程序中使用bind函数将一个套接字和某个端口绑定在一起。
函数原型:int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
sockfd:函数socket创建的套接字。
my_addr:指定了sockfd将绑定到的本地地址。
addrlen:参数my_addr的长度。
返回值:成功返回0,否则-1.
函数用法示例:
struct sockaddr_in serv_addr;
...
if(bind(sock_fd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)) < 0)
{错误提示}
6、套接字 监听(等待请求连接)
函数socket创建的套接字可以用来主动请求连接到某个服务器(connect函数),在服务器端,通常在某个端口监听等待来自客户端的连接请求。
流程:服务器 socket创建套接字----bind函数将套接字绑定到某个端口-----listen将该套接字转化为监听套接字,等待客户端的连接请求。
函数原型:int listen(int sockfd, int backlog);
sockfd:一个已绑定而未被连接的套接字描述符。
backlog:指定该连接队列的最大长度。超过最大长度之后的连接请求被服务器拒绝。
返回值:成功返回0,否则-1.
函数用法示例:
#define LISTEN_MAX 10//定义请求连接队列的长度
...
if(listen(sock_fd, LISTEN_MAX) < 0)
{错误提示}
7、接收一个连接请求
函数accept用来接收一个连接请求。该函数只能对面向连接的套接字使用。
函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd:由函数socket创建,经函数bind绑定到本地的某一端口上,然后通过函数listen转化而来的监听套接字。
addr:用来保存发起连接请求的主机地址和端口。
addrlen:是addr所指向的结构体的大小。
返回值:成功,将创建一个新的套接字,并为这个新的套接字分配一个套接字描述符,并返回这个新的套接字描述符。进程可以利用这个新的套接字描述符与客户端交换数据。
函数用法示例:
int client_len;
struct sockaddr_in client_addr;
...
client_len = sizeof(struct sockaddr_in);
if(accept(sock_fd, ()&client_addr, &client_len) < 0)
{错误提示}
8、TCP套接字的数据传输
1)发送数据
函数send用来在TCP套接字上发送数据,该函数只能对处于连接状态的套接字使用。
函数原型:ssize_t send(int sockfd, const void *msg, size_t len, int flags);
sockfd:为已建立好连接的套接字描述符,即accept函数的返回值。
msg:指向存放待发送数据的缓冲区。
len:待发送数据的长度。
flags:控制选项。一般设置为0,或其他值。取值:MSG_OOB,支持带外数据;MSG_DONTROUTE,忽略下层协议的路由设置。
返回值:成功返回实际发送数据的字节数,否则-1.
2)接收数据
函数recv用来在TCP套接字上接收数据。
函数原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd:指定的套接字描述符
buf:指定的缓冲区
len:缓冲区长度
flags:控制选项,一般为0。其他:MSG_OOB,MSG_PEEK,MSG_WAITALL。
返回值:成功返回接收到的数据字节数,否则-1.
9、UDP套接字的数据传输(不需要套接字处于连接状态)
1)发送数据
ssize_t sendto(int s, const void* msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
...(同send)
to:指定目的地址
tolen:目的地址长度
返回值:成功返回发送数据的字节数,否则-1
2)接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t fromlen);
...
from:保存数据的源地址
fromlen:from的实际大小
10、关闭套接字
头文件:#include <unistd.h>
1)函数close用来关闭一个套接字描述符
函数原型:int close(int fd);
fd:一个套接字的描述符
返回值:成功0,否则-1.
2)函数shutdown也用来关闭一个套接字描述符
函数原型:int shutdown(int sockfd, int how);
sockfd:待关闭的套接字描述符
how:指定关闭的方式。SHUT_RD,SHUT_WR,SHUT_RDWR(读写通道都关闭)。
返回值:成功0,否则-1.
11、系统调用函数
1)字节顺序
大端格式:高字节数据存储在低地址上,低字节数据存储在高地址上(高低)。小端格式,反之(高高,低低)。
TCP/IP协议规定在网络上必须采用网络字节顺序(大端格式)
2)字节顺序转换函数
char型数据可以不用考虑大端小端,如缓冲区数据,非char型的数据需要在数据发送前转换成大端格式,数据接收后在转换成符合主机接收的存储模式。
函数原型:
#include <netinet/in.h>
uint32_t htonl(uint32_t hostlong );//how to network long(htonl),将主机unsigned int型数据转换成网络字节顺序。
uint16_t htons(uint16_t hostshort );//将主机unsigned short型数据转换成网络字节顺序。
uint32_t ntohl(uint32_t hostlong ); //取反
uint16_t ntohs(uint32_t hostlong );//取反
3)网络地址格式转换(inet系列函数)
常使用的网络地址是字符串形式的,如“169.126.254.124”,数据通信时使用的是二进制形式且为网络字节顺序的IP地址,如0x952f155.
函数原型:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
inet_aton();//将字符串形式的IP地址转换成二进制的网络字节顺序地址。
inet_network();//将字符串形式的网络地址,转换为主机字节顺序的二进制IP地址。
inet_ntoa();//将网络字节顺序的二进制IP地址,转换为以“.”分割的字符串形式。
inet_makeaddr();//把两个地址组合成一个网络地址。
inet_lnaof();//取出主机地址
inet_netof();//取出网络地址
4)多路复用方法
C/S模型中,服务器需要同时处理多个客户端连接请求,此时需要用多路复用。
方法:1、非阻塞方式套接字。2、服务器进程不主动询问套接字状态,而是向系统登记希望监视的套接字,然后租塞。
第二种方法
函数原型:int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
n:需要监控的文件描述符个数
readfds:监视可读文件描述符集合,当这个集合中有一个文件描述符到达时,系统会调用select函数。
writefds:监视可写文件描述符集合。
exceptfds:监视异常文件描述符集合。
select:指定阻塞时间。
注:相关宏定义
FD_CLR(int fd, fd_set *set);//将文件描述符fd从文件描述符集合set中删除。
FD_ISSET(int fd, fd_set *set);//测试fd是否在set中。
FD_SET(int fd, fd_set *set);//在文件描述符集合set中增加文件描述符fd。
FD_ZERO(fd_set *set);//将文件描述符集合set清空。
计时结构体:
struct timeval{
long tv_sec;//seconds
long tv_usec;//microseconds
12、getsockopt()
套接字创建以后,就可以利用它来传输数据,但有时可能对套接字的工作方式有特殊要求,此时用此函数来控制套接字的属性。
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
sockfd:一个标识套接口的描述字。
level:选项定义的层次。支持的层次仅有SOL_SOCKET和IPPROTO_TCP。
optname:需获取的套接口选项。
optval:指针,指向存放所获得选项值的缓冲区。
optlen:指针,指向optval缓冲区的长度值。
13、setsockopt()
系统中,如果一个socket绑定一个端口,该socket正常关闭或程序异常退出后的一段时间内,该端口依然保持原来的绑定状态,其他端口无法绑定该端口,如果设置了该选项则可以避免这个问题。
int PASCAL FAR setsockopt(SOCKET s,int level,int optname,const char FAR *optval,int optlen);
s:标识一个套接字的描述符。
level:选项定义的层次;目前仅支持SOL_SOCKET和IPPROTO_TCP层次。
optname:需设置的选项。
optlen:optval缓冲区长度。