套接字
struct sockaddr结构定义了一种通用的套接字地址,他在linux/socket.h中定义如下
struct sockaddr
{
unsigned short sa_family; //套接字的协议簇地址类型,TCP/IP协议对于IPv4地址类型为AF_INET
char sa_data[14];//存储具体的协议地址
};
现在一般使用下面这个sockaddr_in结构(用来设置/获取地址信息):
struct sockaddr_in
{
unsigned short sin_len; //IPv4地址长度
short int sin_family; //指代协议簇,在TCP套接字编程只能是AF_INET
unsigned short sin_port; //存储端口号(使用网络字节顺序),数据类型是一个16为的无符号整形类型
struct in_addr sin_addr;//存储IP地址,IP地址是一个in_add结构体(结构在下面)
unsigned char sin_zero[8]; //为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节
};
其中,in_addr这个数据结构:
struct sockaddr_in sock;
unsistruct sockaddr_in mysock;
sock.sin_family = AF_INET; //TCP地址结构
sock.sin_port = htons(80); //设置端口号为80
sock.sin_addr.s_addr = inet_addr("202.205.3.195"); //设置IP地址
memset(sock.sin_zero ,0 , sizeof(sock.sin_zero)); //将数组sin_zero清零
memset函数的原型为
memset(void * s ,int c ,size_t n);
一.创建套接字
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:用于指定创建套接字所使用的协议族,在头文件<linux/socket.h>中定义。有时候程序中会使用PF_INET,在头文件中AF_INET和PF_INET的数值是一致的。
常见的协议族如下:
AF_UNIX:创建只在本机内进行通信的套接字。
AF_INET:使用IPv4TCP/IP协议
AF_INET6:使用IPv6 TCP/IP协议
说明:AF_UNIX只能用于单一的UNIX系统进程间通信,而AF_INET是针对Interne的,因而可以允许在远程主机之间通信。一般把它赋为AF_INET。
type:指明套接子通信的类型,对应的参数如下
SOCK_STREAM:创建TCP流套接字
SOCK_DGRAM:创建UDP数据报套接字
SOCK_RAW:创建原始套接字
protocol:指定某个协议的特定类型
参数protocol通常设置为0,表示通过参数domain指定的协议族和参数type指定的套接字类型来确定使用的协议。当为原始套接字时,系统无法唯一的确定协议,此时就需要使用使用该参数指定所使用的协议。
返回值:执行成功后返回一个新创建的套接字;若有错误发生则返回一个-1,错误代码存入errno中。
下面用代码创建一个TCP套接字
int sock_fd;
sock_fd = socket(AF_INET ,SOCK_STREAM ,0);
if(sock_fd < 0) {
perror("socket");
exit(1);
}
创建UDP协议的套接字为
sock_fd = socket(AF_INET ,SOCK_DGRAM ,0);
二.建立连接
函数connect用来在一个指定的套接字上创建一个连接,该函数原型
#include <sys/types.h>
#include <sys/socket.h>
int connect (int socket ,const struct sockaddr *serv_addr ,socket_t addrlen);
sock_fd:建立套接字时返回的套接字文件描述符,调用socket()返回的。
serv_addr:是一个指向数据结构sockaddr的指针,其中包括客户端需要连接的服务器的目的IP地址和端口号。
addrlen:表示了第二了参数的大小,可以使用sizeof(struct sockaddr)
执行成功后返回0,有错误发生则返回-1,错误代码存入errno中。
通常一个面对连接的套接字(如TCP套接字)只能调用一次connect函数,而面对无连接的套接字(UDP套接字)则可以多次调用connect函数以改变与目的地址的绑定
客户端在建立套接字之后,不需要进行地址绑定,就可以直接连接服务器。连接服务器的函数为connect(),此函数连接指定参数的服务器,例如IP地址,端口号。
如果是TCP编程,则connect()函数用于服务器发出连接请求,服务器的IP地址和端口号由 参数serv_addr指定。
如果是UDP编程,则connect函数并不建立真正的连接,它只是告诉内核与该套接字进行通信的目的地址(由第二个参数指定),只有该目的地址发来的数据才会被该socket接收。调用connect函数的好处是不必在每次发送和接收数据时都指定目的地址。
该函数常见的用法如下
struct sockaddr_in serv_addr;
memset (&serv_addr , 0 , sizeof(struct sockaddr_in) ); //将serv_addr的各个字段清零
serv_addr.sin_family =AF_INET;
serv_addr.sin_port = htons(80); //htons是字节顺序转换函数,
// inet_aton函数将一个字符串转换为一个网络地址,并把该网络的地址赋给第二个参数
if(inet_aton("172.17.242.131", &serv_addr.sin_addr) < 0) {
perror("inet_ation");
exit(1);
}
//使用sock_fd套接字连接到由serv_addr指定的地址上,假设sock_fd已定义
if(connect(sock_fd, (struct sockaddr *) &serv_addr ,sizeof(struct sockaddr_in )) <0 ) {
perror("connect");
exit(1);
}
注意 serv_addr强制类型转换为struct sockaddr类型
三.绑定套接字
函数bind()的作用是将一个套接字文件描述符与地址和端口绑定。
函数bind用来将一个套接字和某个端口绑定在一起,函数的原型
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd ,struct sockaddr *my_addr ,socklen_t addrlen );
sockfd:sockfd是调用socket函数返回的文件描述符;
addrlen是sockaddr结构的长度。
my_addr: 是一个指向sockaddr结构的指针,它保存着本地套接字的地址(即端口和IP地址)信息。
返回:成功返回0,失败返回-1.
该函数的常用方法
struct sockaddr_in serv_addr;
memset (&serv_addr ,0 , sizeof(struct sockaddr_in ));
serv_addr.sin_family = AF_INEF;
serv_addr.sin_port = htons(80);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if( bind(sock_fd) ,(struct sockaddr *)&serv_addr ,sizeof(struct sockaddr_in ) < 0) {
perror("bind");
exit(1);
}
四.在套接字上监听
函数listen 把套接字转化为被动监听,listen在套接字函数中表示让一个套接字处于监听到来的连接请求的状态,该函数原型
#include <sys/socket.h>
int listen (int s , int backlog);
无错误,返回0,
否则,返回SOCKET ERROR,可以调用函数WSAGetLastError取得错误代码。
listen函数使用主动连接套接字变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。在TCP服务器编程中listen函数把进程变为一个服务器,并指定相应的套接字变为被动连接。
listen函数一般在调用bind之后-调用accept之前调用。
该函数常见用法
#define LISTEN_NUM 12 //定义连接请求队列长度
....
if(listen (sock_fd , LISTEN_NUM) < 0) {
perror ("listen ");
exit(1);
}
五.接受连接
函数accept用来接受一个连接请求,该函数原型
#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,错误代码存入errno中,详细带错误代码手册参考man手册
返回值:accept()函数的返回值是新连接的客户端套接字文件描述符,与客户端之间的通信是通过accept()返回的新套接字文件描述符来进行的,而不是通过建立套接字时的文件描述符。如果accept()函数发生错误,accept()会返回-1,通过errno可以得到错误值。
如果参数sock_fd所指定的套接字被设置为阻塞方式(Linux下的默认方式),且连接请求队列为空,则accept()将被阻塞直到有连接请求到此为止;如果参数s所指定的套接字被设置为非阻塞方式,如果队列为空,accept将立即返回-1,errno被设置为EAGAIN.
套接字为阻塞方式下该函数的常见方法
int client_fd;
int client_len;
struct sockaddr_in client_addr;
...
client_len = sizeof(struct sockaddr_in);
client_fd = accept(sock_fd , (struct sockaddr *)&client_addr , &client_len );
if (conn_fd < 0) {
perror("accpt");
exit(1);
}