1.套接字地址结构
结构体sturct sockaddr定义了一种通用的套接字地址,它在linux/socket.h中的定义代码如下:
struct sockaddr
{
unsigned short sa_family;
char sa_data[14];
}
其中,成员sa_family表示套接字的协议族类型,对于TCP/IP协议该值为AF_INET;成员sa_data储存具体的协议地址. sa_data之所以被定义成14个字节,因为有的协议族使用较长的地址格式.一般编程中并不对该结构体进行操作,而是使用另一个与它等价的数据结构:sockadd_in.
每一种协议族都有自己的协议地址格式,TCP/IP协议族的地址格式为结构体struct socketadd_in.他在nettinet/in.h中定义格式如下:
struct sockaddr_in
{
unsigned short sin_family; //地址类型
unsigned short int sin_port; //端口号
struct in_addr sin_addr; //IP地址
unsigned char sin_zero[8]; //填充字节
}
其中,成员sin_family表示地址类型,对于使用TCP/IP的协议进行网络编程,该值只能是AF_INET.sin_port为端口号,sin_addr用来储存32位IP地址,数组sin_zero为填充字段,一般赋值为0.
struct in_addr 的定义如下:
struct in_addr
{
unsigned long s_addr;
}
结构体sockadr的长度为16字节,结构体sockaddr_in的长度也为16字节.通常在编写基于TCP/IP协议的网络程序时,使用结构体sockaddr_in来设置地址,然后通过强制类型转换成sockaddr类型.
以下是设置地址信息的示例代码:
struct sockaddr_in sock;
sock.sin_family = AF_INET;
sock.sin_port = htons(80); //设置端口号80
sock.sin_addr.s_addr = inet_addr("114.114.114.1");
memset(sock.sin_zero, 0, sizeof(sock.sin_zero));
memset函数原型为:
memset(void * s, int c, size_t n);
它将s指向的内存区域的前n个字节赋值由参数c的值指定.
2.创建套接字
1.函数原型:
int socket(int domain, int type, int protocol);
2.参数说明:
(1)domain:协议域,又称协议族(family)。AF 表示ADDRESS FAMILY 地址族.常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
(2)type:指定Socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用。数据报式Socket(SOCK_DGRAM)是一种无连接的Socket,对应于无连接的UDP服务应用。
(3)protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
注意:1.type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当第三个参数为0时,会自动选择第二个参数类型对应的默认协议。
(4)返回值:
如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET(Linux下失败返回-1)。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。每个进程在自己的进程空间里都有一个套接字描述符表但是套接字数据结构都是在操作系统的内核缓冲里。
参数domain用于指定创建套接字所示哟你的协议族,他们在linux/socket.h中定义,常用协议族如下
- AF_UNKIX:创建只在本机内进行通信的套接字.
- AF_INET: 使用IPv4 TCP/IP协议.
- AF_INET6:使用IPv6 TCP/IP协议.
参数type指定套接字类型可取如下值:
- SOCK_STREAM:创建TCP流套接字.
- SOCK_DGRAM:创建UDP数据报套接字.
- SOCK_RAW:创建原始套接字.
参数protocol通常设置为0,表示通过参数domain指定的协议族和参数type指定的套接字类型确定使用的协议.
执行成功返回一个新创建的套接字;若有错误发生则返回-1.
下面代码创建一个TCP套接字:
int sock_fd;
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if(sock_fd < 0)
{
perrno("socket");
exit(1);
}
创建UDP套接字为:
sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
3.建立连接
函数connect用来在一个指定的套接字上创建一个连接
#include<sys/types.h>
#include<sys/socket.h>
int connetc(int sockfd, const struct sockaddr * serv_addr, socklen_t addrlen);
本函数用于创建与指定外部端口的连接。sockfd参数指定一个未连接的数据报或流类套接口。如套接口未被捆绑,则系统赋给本地关联一个唯一的值,且设置套接口为已捆绑。请注意若名字结构中的地址域为全零的话,则connect()将返回WSAEADDRNOTAVAIL错误。
对于流类套接口(SOCK_STREAM类型),利用名字来与一个远程主机建立连接,一旦套接口调用成功返回,它就能收发数据了。对于数据报类套接口(SOCK_DGRAM类型),则设置成一个缺省的目的地址,并用它来进行后续的send()与recv()调用。
参数sockfd是一个由函数socket创建的套接字.如果该套接字的类型是SOCK_STREAM,则connect函数用于向服务器发送连接请求,服务器的IP地址和端口号由参数serv_addr指定.如果套接字的类型是SOCK_DGRAM, 则connect函数的好处是不必在每次发送和接受数据时都指定目的地址.
通常一个面向连接的套接字(如TCP套接字)只能调用一次connect函数,而对于无连接的套接字(如UDP套接字)则可以多次调用connect函数以改变与目的地址的帮顶.将参数serv_addr中的sa_family设置为AF_UNSPEC可取消绑定. 执行成功返回0,有错误则返回-1.
该函数的常用方法如下:
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(struct sockaddr_in));
serv_addr.sin_faminy = AF_INET;
serv_addr.sin_port = htons(80);//字节转换函数
if((inet_aton("172.17.242.131", &serv_addr.sin_addr) < 0);
{
perror("inet_aton");
exit(1);
}
if(connetc(sock_fd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr_in)) < 0)
{
perror("connect");
exit(1);
}
4.绑定套接字
函数band用来将一个套接字和某一个端口绑定在一起
函数原型:
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, struct sockaddr* my_addr, socklen_t address_len);
参数说明:
socketfd:是一个套接字描述符。
address:是一个sockaddr结构指针,该结构中包含了要结合的地址和端口号。
address_len:确定address缓冲区的长度。
返回值:
如果函数执行成功,返回值为0,否则为SOCKET_ERROR。
socket函数只是创建了一个套接字,这个套接字将工作在那个端口上,程序并没有指定,在客户机/服务器模型中,服务器端的IP地址和端口号一般是固定的,因此在服务器的程序中,使用bind函数将一个套接字和某个端口绑定在一起,该函数一般只由服务器调用.
参数my_addr指定了sockfd将绑定到的本地地址,可以将参数my_addr的sin_addr设置为INADDR_ANY而不是某个确定的IP就可以绑定到任何网络接口, 对于只有一个IP地址的计算机,INADDR_ANY对应的就是它的IP地址;对于多宿主主机(拥有多块网卡), INADDR_ANY表示服务器程序将处理来自所有网络接口上相应端口的连接请求.
成功返回0,错误返回-1.
该函数的常见用法如下:
struct sockadd_in serv_addr;
memset(&serv_addr, 0, sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(80);
serv_addr.sin_addr.s_addr = hton1(INADDR_ANY);
if(bind(sock_fd, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr_in)) < 0)
{
perror("bind");
exit(1);
}
5.在套接字上监听
函数listen把套接字转化为被动监听,函数原型为:
#include<sys/socket.h>
int listen(int s, int backlog);
有函数socket创建的套接字是主动套接字,这种套接字可以用来主动请求连接到某个服务器(通过函数connect).但是作为服务器端的程序,通常在某个端口上监听等待来自客户端的连接请求,在服务器端,一般是先调用socket函数创建一个主动套接字,然后调用函数bind将套接字绑定到某个端口上,接着再调用函数listen将该套接字转化为监听套接字,等待来自客户端的连接请求.
一般多个用户连接到一个服务器,服务器向这些客户提供某种服务.服务器端设置一个连接队列,记录已经建立的连接,参数badklog指定了该连接队列的最大长度.如果连接队列已经达到最大,之后的连接请求将被服务器拒绝. 执行成功返回0,发生错误返回-1.
注意:函数listen只是将套接字设置为倾听模式以等待连接请求,它并不能接受连接请求,真正接受客户端连接请求的是后面即将介绍的函数accept().
6.接受连接
函数accept用来接受一个连接请求.原型如下:
#include<sys/types.h>
#include<sys/socket.h>
int accept(int s, struct sockaddr * addr, socklen_t * addrlen);
参数s是由函数socket创建,经函数bgind绑定到本地的某一端口上,然后通过函数listen转化而来的监听套接字.
- 参数addr用来保存发起连接请求的主机连接端口.
- 参数addrlen是addr所指向的结构体的大小.
执行成功返回一个新的代表客户端的套接字, 出错返回-1
只能对面向连接的套接字使用accept函数.accept函数执行成功时,将创建一个新的套接字,并且为这个新的套接字分配一个套接字描述符,并返回这个新的套接字描述符, 这个新的套接字描述符与打开文件时返回的文件描述符类似,进程可以利用这个新的套接字描述符与客户端交换数据, 参数s所指的套接字描述符被设置为阻塞方式(linux下默认方式)且连接请求列队为空, 则accepet() 将被阻塞直到有连接请求达到为止,如果参数s所指的套接字被设置为非阻塞方式,则如果队列为空, accept立刻返回-1,errno 被设置为EAGAIN.
套接字为阻塞方式下该函数的常见用法:
int client_fd;
int client_len;
struct sockaddr_in client_addr;
//........socket() band() listen()
client_len = sizeof(struct sockaddr_in);
client_fd = accept(sock_fd, (stuct sockaddr *)&client_addr, &client_len);
if(conn_fd < 0)
{
perror("accept");
exit(1);
}
7.TCP套接字的数据传输
(1)发送数据
函数send() 用来在TCP套接字上发送数据
函数原型为:
#include<sys/types.h>
#include<sys/socket.h>
ssize_t send(int s , const void * msg, size_t len , int flags);
函数send只能对处于连接状态的套接字使用
参数:
s:已建立好连接的套接字描述符, 即accept函数的返回值
msg: 指向存放带发送逐句的缓冲区
len : 待发送的数据长度.
flag:控制选项 一般设置为0或取以下宏:(并不知道是什么意思)
- MSG_OOB:在指定的套接字上发送带外数据(out-of-band-data),该类型的套接字必须支持带外数据(SOCK_STREAM).
- MSG_ DONTROUTE:通过最直接的路径发送数据,而忽略下层协议的路由设置
如果要发送的数据太长而不能发送时, 将出现错误, errno:EMSGSIZE,如果要发送的数据长度大于该套接字的缓冲区剩余空间大小时,send会一直被阻塞,如果该套接字被设置为非阻塞方式,此时立刻返回-1,error:EAGAIN.
返回值:
执行成功返回实际发送数据的字节数, 出错返回-1.
套接字为阻塞方式下该函数的常见用法:
#define BUFFERSIZE 1500
char send_buf[BUFFERSIZE];
//..
if(send(conn_fd, send_buf, len, 0) < 0);
{
perror("send");
edxit(1);
}
(2).接收数据
函数recv 用来在TCP套接字上接受数据,
函数原型为:
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int s , void * buf , size_t len , int flags);
函数recv从参数s所指向的套接字描述符(必须是免洗那个连接的套接字)上接收数据并保存到参数buf所指定的缓冲区,参数len 则为缓冲区长度.
参数说明:
socket:一个标识已连接套接口的描述字。
buf :用于接收数据的缓冲区。
len :缓冲区长度。
flags :指定调用方式。取值:
- MSG_PEEK 查看当前数据,数据将被复制到缓冲区中,但并不从输入队列中删除;
- MSG_OOB 处理带外数据。
- MSG_WAITALL:只有在接收缓冲区满时才返回
如果一个数据包太长以至于缓冲区不能完全放下时,剩余部分数据肯呢个将被丢放弃(根据接受数据的套接字类型而定).如果在指定的套接字上无数据到达时,recv()将被阻塞,如果该套接字被设置为非阻塞方式,则立即返回-1,函数recv接收到数据就返回,并不会等待接收到参数len指定的长度数据才返回.
返回值:
若无错误发生,recv()返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。
参数说明:
sockfd:标识一个已连接套接口的描述字。
buf:接收数据缓冲区。
len:缓冲区长度。
flags:调用操作方式。是以下一个或者多个标志的组合体,可通过or操作连在一起:
(1)MSG_DONTWAIT:操作不会被阻塞;
(2)MSG_ERRQUEUE: 指示应该从套接字的错误队列上接收错误值,依据不同的协议,错误值以某种辅佐性消息的方式传递进来,使用者应该提供足够大的缓冲区。导致错误的原封包通过msg_iovec作为一般的数据来传递。导致错误的数据报原目标地址作为msg_name被提供。错误以sock_extended_err结构形态被使用。
(3)MSG_PEEK:指示数据接收后,在接收队列中保留原数据,不将其删除,随后的读操作还可以接收相同的数据。
(4)MSG_TRUNC:返回封包的实际长度,即使它比所提供的缓冲区更长, 只对packet套接字有效。
(5)MSG_WAITALL:要求阻塞操作,直到请求得到完整的满足。然而,如果捕捉到信号,错误或者连接断开发生,或者下次被接收的数据类型不同,仍会返回少于请求量的数据。
(6)MSG_EOR:指示记录的结束,返回的数据完成一个记录。
(7)MSG_TRUNC:指明数据报尾部数据已被丢弃,因为它比所提供的缓冲区需要更多的空间。
(8)MSG_CTRUNC:指明由于缓冲区空间不足,一些控制数据已被丢弃。
(9)MSG_OOB:指示接收到out-of-band数据(即需要优先处理的数据)。
(10)MSG_ERRQUEUE:指示除了来自套接字错误队列的错误外,没有接收到其它数据。
from:(可选)指针,指向装有源地址的缓冲区。
fromlen:(可选)指针,指向from缓冲区长度值。