(1) socket套接字
是一个编程接口
是一种特殊的文件描述符(起源于Unix,“everything in Unix is a file”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作)
是应用层与TCP/IP协议族通信的中间软件抽象层
在设计模式中,Socket其实就是一个门面模式(系统对外界提供单一的接口,外部不需要了解内部的实现)
并不仅限于TCP/IP协议
面向连接(Transmission Control Protocol - TCP/IP)
无连接(User Datagram Protocol-UDP 和 Inter-Network Packet Exchange-IPX)
Socket层级说明
(2) 常见的socket有3种类型
<1> 流式socket(SOCK_STREAM )
流式套接字提供可靠的、面向连接的通信流;它使用TCP 协议,从而保证了数据传输的正确性和顺序性。
<2> 数据报socket(SOCK_DGRAM )
数据报套接字定义了一种无连接的服 ,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议UDP。
<3> 原始socket(SOCK_RAW)
原始套接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
(3) 面向TCP数据流的socket通信 + 面向UDP数据报的socket通信
TCP客户-服务器程序设计基本框架
TCP三次握手四次挥手时序图
UDP客户-服务器程序设计基本框架
(4) 常用函数
1.socket函数:为了执行网络输入输出,一个进程必须做的第一件事就是调用socket函数获得一个文件描述符
int socket(int family, int type, int protocol);
————————————————————————————————————
family:指明了协议族/域,通常AF_INET、AF_INET6、AF_LOCAL等;
type:是套接口类型,主要SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;protocol:一般取为0。成功时,返回一个小的非负整数值,与文件描述符类似。
————————————————————————————————————
返回值:非负描述符 – 成功,-1 - 出错
2.bind函数:为套接口分配一个本地IP和协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合;如指定端口为0,调用bind时内核将选择一个临时端口,如果指定一个通配IP地址,则要等到建立连接后内核才选择一个本地IP地址。
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
————————————————————————————————————
sockfd:是socket函数返回的描述符;
myaddr:指定了想要绑定的IP和端口号,均要使用网络字节序-即大端模式;
addrlen:是前面struct sockaddr(与sockaddr_in等价)的长度。
————————————————————————————————————
返回值:0 – 成功,-1 - 出错并将errno置为相应的错误号
3.connect函数:该函数用于绑定之后的client端,与服务器建立连接
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
————————————————————————————————————
sockfd:是本地sockect描述符
serv_addr:是服务器端的IP地址和端口号的地址
addrlen:常被设置为sizeof(struct sockaddr)。
————————————————————————————————————
// 通过此函数建立于TCP服务器的连接,实际是发起三次握手过程,仅在连接成功或失败后返回。参数sockfd是本地描述符,addr为服务器地址,addrlen是socket地址长度。
// UDP的connect函数,结果与tcp调用不相同,没有三次握手过程。内核只是记录对方的ip和端口号,他们包含在传递给connect的套接口地址结
构中,并立即返回给调用进程。
返回值:0 – 成功,-1 - 出错并且errno中包含相应的错误码
4.listen函数:设置能处理的最大连接数,listen()并未开始接受连线,只是设置sockect为listen模式
int listen(int sockfd, int backlog);
————————————————————————————————————
sockfd:是socket系统调用返回的服务器端socket描述符
backlog:指定在请求队列中允许的最大请求数
————————————————————————————————————
对于给定的监听套接口,内核需要维护两个队列:未完成连接队列+已完成连接队列。 未完成连接队列:服务端正在等待完成TCP
相应的三次握手,处于SYN_RCVD状态;已完成连接队列:为每个已完成TCP三次握手的客户开设一个条目,处于ESTABLISHED状态。
返回值:0 – 成功,-1 - 出错
5.accept函数:用来接受socket连接,由TCP服务器调用,从已完成连接队列头返回一个已完成连接,如果完成连接队列为空,则进程进入睡眠状态
int accept(int sockfd, struct sockaddr *addr, int *addrlen);
————————————————————————————————————
sockfd:是服务器socket描述符
addr:通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求的客户端地址
addrten:通常为一个指向值为sizeof(struct sockaddr_in)的整型指针变量。
————————————————————————————————————
// 第一个套接字称为监听套接字,返回值称为已连接套接字;一个给定的服务器常常只生成一个监听套接口且一直存在直到服务器
关闭,内核为每个被接受的客户连接创建了一个已连接套接口(内核为它完成了TCP三次握手过程);当服务器完成某客户服务,关
闭已连接套接口。
返回值:成功-非负描述符,-1—错误并且设置相应的errno值
6.send函数:TCP发送数据
int send(int sockfd, const void *buf, int len, int flags);
————————————————————————————————————
sockfd:发送端套接字描述符(非监听描述符)
buf:应用要发送数据的缓存
len:实际要发送的数据长度
flags:一般设置为0
————————————————————————————————————
返回值:
>0 – 成功拷贝至发送缓冲区的字节数(可能小于len),
-1 – 出错,并置错误号errno.
// 如果send在等待协议发送数据时出现网络断开的情况,则会返回-1。注意:send成功返回并不代表对方已接收到数据,如果后续
的协议传输过程中出现网络错误,下一个send便会返回-1发送错误。TCP给对方的数据必须在对方给予确认时,方可删除发送缓冲区
的数据。否则,会一直缓存在缓冲区直至发送成功(TCP可靠数据传输决定的)
7.recv函数:TCP接受数据
int recv(int sockfd,void *buf,int len,unsigned int flags);
————————————————————————————————————
sockfd:接收端套接字描述符;
buf:指定缓冲区地址,用于存储接收数据;
len:指定的用于接收数据的缓冲区长度;
flags:一般指定为0
————————————————————————————————————
返回实际上接收的字节数,或当出现错误时,返回-1并置相应的errno值。
// 阻塞模式下,recv/recvfrom将会阻塞到缓冲区里至少有一个字节(TCP)/至少有一个完整的UDP数据报才返回,没有数据时处于休
眠状态。若非阻塞,则立即返回,有数据则返回拷贝的数据大小,否则返回错误-1,置错误码为EWOULDBLOCK。
8.sendto函数:UDP发送数据,用于面向非连接的socket(SOCK_DGRAM/SOCK_RAW)
int sendto(int sockfd, const void *buf,int len,unsigned int flags,const struct sockaddr *to,int tolen);
该函数比send()函数多了两个参数
————————————————————————————————————
to:表示目地机的IP地址和端口号信息
tolen:常常被赋值为sizeof (struct sockaddr)。
————————————————————————————————————
返回实际发送的数据字节长度,在出现发送错误时返回-1。
// 当本地与不同目的地址通信时,只需指定目的地址,可使用同一个UDP套接口描述符sockfd,而TCP要预先建立连接,每个连接都
会产生不同的套接口描述符,体现在:客户端要使用不同的fd进行connect,服务端每次accept产生不同的fd。
// 因为UDP没有真正的发送缓冲区,是不可靠连接,不必保存应用进程的数据拷贝,应用进程中的数据在沿协议栈向下传递时,以某
种形式拷贝到内核缓冲区,当数据链路层把数据传出后就把内核缓冲区中数据拷贝删除。因此它不需要一个发送缓冲区。写UDP套接
口的sendto/write返回表示应用程序的数据或数据分片已经进入链路层的输出队列,如果输出队列没有足够的空间存放数据,将返
回错误ENOBUFS.
9.recvform函数:UDP接受数据,用于面向非连接的socket(SOCK_DGRAM/SOCK_RAW)
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
————————————————————————————————————
sockfd:接收端套接字描述
buf:用于接收数据的应用缓冲区地址
len:指名缓冲区大小
flags:通常为0
from:是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号
fromlen:常置为sizeof(struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数
————————————————————————————————————
返回接收到的字节数或当出现错误时返回-1,并置相应的errno
// 阻塞模式下,recv/recvfrom将会阻塞到缓冲区里至少有一个字节(TCP)/至少有一个完整的UDP数据报才返回,没有数据时处于休
眠状态。若非阻塞,则立即返回,有数据则返回拷贝的数据大小,否则返回错误-1,置错误码为EWOULDBLOCK。
10.close函数
close缺省功能是将套接字作“已关闭”标记,并立即返回到调用进程,该套接字描述符不能再为该进程所用:即不能作为read和
write(send和recv)的参数,但是TCP将试着发送发送缓冲区内已排队待发的数据,然后按正常的TCP连接终止序列进行操作(断开
连接4次握手)。
11.shutdown函数
shutdown不仅可以灵活控制关闭连接的读、写或读写功能,而且会立即执行相应的断开动作(发送终止连接的FIN分节等),此时不
论有多少进程共享此套接字描述符,都将不能再进行收发数据。
12.IP地址转换函数
inet_aton() 和 inet_ntoa()
int inet_aton(const char *strptr, struct in_addr *addrptr);
将strptr所指的字符串转换成32位的网络字节序二进制值
char *inet_ntoa(struct in_addr inaddr);
将32位网络字节序二进制地址转换成点分十进制的字符串
in_addr_t inet_addr(const char *strptr);
inet_network()
in_addr_t inet_network(const char *cp);
功能同上,将字符串形式转换为整数形式-主机字节序
inet_pton() 和 inet_ntop()
int inet_pton(int af, const char *src, void *dst);
转换字符串到网络地址,将“点分十进制” -> “整数”
const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);
转换网络二进制结构到ASCII类型的地址
13. 字节序转换函数
主机字节序到网络字节序:
u_long htonl(u_long hostlong);
u_short htons(u_short short);
网络字节序到主机字节序
u_long ntohl(u_long hostlong);
u_short ntohs(u_short short);
(5) 简单的通信实例
1. 面向TCP字节流的socket通信
TCP客户端:
//int socket(int domain, int type, int protocol);
//int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//int listen(int sockfd, int backlog);
//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//uint16_t htons(uint16_t hostshort);
//int atoi(const char *nptr);
//in_addr_t inet_addr(const char *cp);
//void bzero(void *s, size_t n);
//int listen(int sockfd, int backlog);
//int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
#include
#include
#include
#include //Unix/Linux系统的基本系统数据类型的头文件
#include //socket header file
#include //socketaddr_in 结构体 + htons系统调用
#include //inet_pton,inet_aton,inet_addr函数
#include //POSIX标准定义的unix类系统定义符号常量的头文件,包含了许多UNIX系统服务的函数原型,例如read函数、write函数和getpid函数
#include //close,open,create
#include //进程间共享内存
#define MYPORT 1234
#define BUFFER_SIZE 1024
int main()
{
//定义sockfd
int sock_client = socket(AF_INET,SOCK_STREAM,0);
//定义sockaddr_in
struct sockaddr_in serveraddr; //描述服务器地址: 类型、ip地址、端口结构体
memset(&serveraddr,0,sizeof(serveraddr)); //客户程序填充服务端的资料
serveraddr.sin_family = AF_INET; //地址族
serveraddr.sin_port = htons(MYPORT); //服务器端口
serveraddr.sin_addr.s_addr = inet_addr("123.321.121.212"); //服务器ip;客户端connect时,不能使用INADDR_ANY选项,必须指明要连接哪个服务器IP
//客户程序发起连接请求,连接服务器,成功返回0,错误返回-1
//sock_client本地描述符;(struct sockaddr *)强制转换成sockaddr给操作系统用
if (connect(sock_client,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) < 0)
{
perror("connect is not built!");
exit(1); //0表示正常退出,其他返回值表示非正常退出
}
char sendbuf[BUFFER_SIZE];
char recvbuf[BUFFER_SIZE];
while (fgets(sendbuf,sizeof(sendbuf),stdin) != NULL)
{
send(sock_client,sendbuf,strlen(sendbuf),0); //发送
if (strcmp(sendbuf,"exit\n") == 0)
break;
recv(sock_client,recvbuf,sizeof(recvbuf),0); //接收
fputs(recvbuf,stdout);
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
//结束通讯
close(sock_client);
return 0;
}
#include //socket();bind();listen():accept();listen();accept();connect();
#include //socket();bind();listen():accept();inet_addr();listen():accept();connect();
#include
#include //inet_addr()
#include //htons();inet_addr()
#include //close()
#include
#include //atoi();exit();
#include
#include
#define MYPORT 1234
#define QUEUE 20
#define BUFFER_SIZE 1024
int main()
{
///定义sockfd
int sockfd_server = socket(AF_INET,SOCK_STREAM,0);
///定义sockaddr_in
struct sockaddr_in server_sockaddr; //服务器端填充 sockaddr结构
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(MYPORT);
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); //这个地址表示不确定地址,或“所有地址”、“任意地址”;多网卡的情况下表示所有网卡ip地址
///bind,成功返回0,出错返回-1
if (bind(sockfd_server,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr)) == -1)
{
perror("bind is not built!");
exit(1);
}
///listen,成功返回0,出错返回-1 设置允许连接的最大客户端数Queue
if (listen(sockfd_server,QUEUE) == -1)
{
perror("listen is not built!");
exit(1);
}
///客户端套接字
char buffer[BUFFER_SIZE];
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
while(1)
{
///成功返回非负描述字,出错返回-1 服务器阻塞,直到客户程序建立连接
int conn_sock = accept(sockfd_server,(struct sockaddr *)&client_addr,&length);
if (conn_sock < 0) //成功-非负描述符
{
perror("connect is not built!");
exit(1);
}
memset(buffer,0,sizeof(buffer));
int recv_len = recv(conn_sock,buffer,sizeof(buffer),0);
if (strcmp(buffer,"exit\n") == 0)
break;
fputs(buffer,stdout);
send(conn_sock,buffer,recv_len,0);
/* 这个通讯已经结束 */
close(conn_sock);
/* 循环下一个 */
}
/* 结束通讯 */
close(sockfd_server);
return 0;
}
2. 面向UDP数据报的socket通信
UDP客户端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 1234
#define MAX_BUF_SIZE 1024
int main(int argc,char **argv)
{
//建立 sockfd描述符
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
fprintf(stderr,"Socket is not built:%s\n",strerror(errno));
exit(1);
}
//填充服务端的资料
struct sockaddr_in serv_addr; //定义服务器地址
memset(&serv_addr, 0, sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVER_PORT);
if (inet_aton(argv[0], &serv_addr.sin_addr) == 0) //手动输入服务器IP; 字符串型的IP地址转化成网络字节序的2进制IP地址; 不正确返回零
{
fprintf(stderr,"IP error:%s ",strerror(errno));
exit(1);
}
//or
//serv_addr.sin_addr.s_addr = inet_addr("123.321.121.212");
/* if(argc!=2)
{
fprintf(stderr,"Usage:%s server_ip\n",argv[0]);
exit(1);
}*/
//数据发送
char send_buf[MAX_BUF_SIZE];
int addr_len = sizeof(struct sockaddr_in);
while(1)
{
printf("pls input string:\n");
fgets(send_buf,MAX_BUF_SIZE,stdin);
sendto(sockfd, send_buf, strlen(send_buf), 0, (struct sockaddr *)&serv_addr, addr_len);
bzero(send_buf,MAX_BUF_SIZE);
}
close(sockfd);
}
UDP服务器:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 1234
#define MAX_MSG_SIZE 1024
int server(void)
{
int sockfd;
struct sockaddr_in serv_addr; //定义服务器地址
//建立socket描述符
if ((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0)
{
fprintf(stderr,"Socket is not created! %s\n",strerror(errno));
exit(1);
}
//填充服务端的资料
memset(&serv_addr,0,sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVER_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//捆绑sockfd描述符
if (bind(sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in)) < 0)
{
fprintf(stderr,"Bind error:%s\n",strerror(errno)); //输出错误类型
exit(1);
}
unsigned int addr_len = sizeof(struct sockaddr_in);
char msg[MAX_MSG_SIZE];
while(1)
{
//从服务器设置的对应端口上读并写到接收缓存
int copy_len = recvfrom(sockfd, msg, MAX_MSG_SIZE, 0, (struct sockaddr *)&serv_addr, &addr_len); //返回拷贝的数据大小 recv会阻塞,如果有输入就会一直等待输入完成
//显示服务端已经收到了信息
fprintf(stdout,"Server has received %s ",msg);
bzero(msg,sizeof(msg));
}
close(sockfd);
return 0;
}