socket编程是网络常用的编程,我们通过在网络中创建socket关键字来实现网络间的通信。
1.TCP/IP协议
先来简单了解一下TCP/IP协议:
iso7层架构
应用层 | 应用层不仅要提供应用进程所需要信息交换和远程操作,而且还要作为应用进程的用户代理,完成一些为进行语义上有意义的信息交换所必须的功能。 |
---|---|
表示层 | 用于处理两个通信系统间信息交换的表示方式,它包括数据格式变换、数据加密与解密、数据压缩与恢复等功能。 |
会话层 | 组织同步的两个会话用户之间的对话,并管理数据的交换。 |
传输层 | 向用户提供可靠的端到端服务,透明地传送报文。 |
网络层 | 通过执行路由选择算法,为报文分组通过通信子网选择最适当的路径。 |
数据链层 | 在物理层提供比特流传输服务的基础上,在通信实体之间建立数据链路连接,传送以帧为单位的数据 |
物理层 | 物理层的主要功能是利用物理传输介质为数据链路层提供物理连接,以透明地传送比特流。 |
不同于iso7层架构,TCP/IP协议分为4层
应用层 | TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等 |
---|---|
传输层 | TCP,UDP |
网络层 | IP,ICMP,OSPF,EIGRP,IGMP |
数据链层 | SLIP,CSLIP,PPP,MTU |
在tcp/ip协议中,tcp通过三次握手建立起一个tcp的链接
第一次握手:客户端尝试连接服务器,向服务器发送syn包,syn=j,客户端进入SYN_SEND状态等待服务器确认。
第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
socket就在应用程序的传输层和应用层之间,设计了一个socket抽象层,传输层的底一层的服务提供给socket抽象层,socket抽象层再提供给应用层。
2.socket编程
(1)套接字的创建和销毁
使用socket函数创建一个套接字
#include
int socket(int domain,int type,int protocol)
参数domain(域)确定通信的特性
域 | 描述 |
---|---|
AF_INET | IPV4因特网域 |
AF_INET6 | IPV6因特网域 |
AF_UNIX | UNIX域 |
AF_UPSPEC | 未指定 |
参数type确定套接字的类型
类型 | 描述 |
---|---|
SOCK_DGRAM | 固定长度的,无连接的,不可靠的报文传递 |
SOCK_RAM | IP协议的数据包接口 |
SOCK_SEQPACKET | 固定长度的,有序的,可靠的,面向连接的报文传递 |
SOCK_STREAM | 有序的,可靠的,双向的,面向连接的的字节流 |
参数protocol通常是0,表示为给定的域和套接字类型选定默认的协议。
调用套接字与调用open函数相似,均可获得用于I/O的文件描述符。当不再需要该文件符时,调用close函数来关闭对文件和或套接字的访问。
套接字的通信是双向的,可以采用函数shutdown来禁止一个套接字的I/O。
#include
int shutdown(int sockfd,int how);
返回值:成功返回0,出错返回-1
参数how:SHUT_RD关闭读端,SHUT_WR关闭写端
(2).字节序
大端字节序:最大字节地址出现最低有效字节
小端字节序:最小字节地址在最低有效字节
对于32位整数0X04030201,最高有效字节为04,最低为01。采用大端,则最大字节地址包含01。反之则最小字节地址包含01。
网络协议指定了字节序,对于TCP/IP协议栈,使用大端字节序。
应用程序需要在处理器字节序与网络字节序之间转换它们。对于TCP/IP应用程序,有四个用来转换的函数。
#include
uint32_t htonl(uint32_t hostint32); //host to net 将主机字节序转换为32位网络字节序
uint16_t htons(uint16_t hostint16); //host to net 将主机字节序转换为16位网络字节序
uint32_t ntohl(uint32_t netint32); //net to host 将网络字节序转换为32位主机字节序
uint16_t ntohs(uint16_t netint16); //net to host 将网络字节序转换为16位主机字节序
(3)地址格式
在linux下,套接字地址用结构体sockaddr_in表示
struct sockaddr_in {
short sin_family; //地址类型,ipv4或ipv6
unsigned short sin_port; //端口
struct in_addr sin_addr; //ip地址
unsigned char sin_zero[8]; //无意义,用来补全16位字节
};
(4)将套接字与地址关联
给服务器器关联上一个众所周知的地址。
使用函数bind来关联地址和套接字
#include
int bind(int sockfd,const struct sockaddr *addr,socklen_t len)
返回值:成功返回0,失败返回-1
参数sockfd:是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。参数addr :是赋给套接字s的本地地址(名字),其长度可变,结构随通信域的不同而不同。
参数len:表明了addr的长度。
(5)建立套接字连接
函数connect
#include
int connect(int sockfd,const struct sockaddr *addr,socklen_t len)
返回值:成功返回0,失败返回-1
参数sockfd:是本地套接字描述符
参数addr是服务器地址
参数len:是服务器地址长度
函数listen
服务器调用listen函数来宣告它愿意接受请求
#include
int listen(int sockfd,int backlog)
返回值:成功返回0,出错返回-1
参数sockfd:标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求。
参数backlog:提示系统该进程所要入队的未完成连接请求数量。队列满,服务器就会拒绝多余的连接请求。
函数accept
一旦服务器调用了lieten,使用的套接字就能接受连接请求。使用accept来获得连接请求,并建立连接。
#include
int accept(int sockfd,struct sockaddr *restrict addr,socklen_t *restrict len)
返回值:成功返回描述符,出错返回-1
参数sockfd:本地套接字描述符
参数addr:客户端套接字地址
(6)数据传输
有6个函数用于数据传输,3个发送,3个接受。这里我就只写两个
函数send
#include
ssize_t send(int sockfd,const void *buf,size_t nbytes,int flags);
返回值:成功返回字节数,失败返回-1
send函数与write函数相似,多了一个flags参数
总结一下这些flag
MSG_CONFIRM | 提供链路层反馈以保持地址映射有效 |
---|---|
MSG_DONTROUTE | 勿将数据包路由出本地网络 |
MSG_DONTWAIT | 允许非阻塞操作 |
MSG_EOR | 如果协议支持,标记记录结束 |
MSG_MORE | 延迟发送数据包,允许写更多数据 |
MSG_NOSIGNAL | 在写无连接的套接字时不产生SIGPIPE信号 |
MSG_OOB | 如果协议支持,发送带外数据 |
函数recv
#include
ssize_t recv(int sockfd,void *buf,size_t nbytes,int flags)
与read函数相似,多了一个flags控制标志
MSG_GMSG_CLOEXEC | 位unix域套接字上接收的文件描述符设置执行时关闭标志 |
---|---|
MSG_DONTWAIT | 启用非阻塞操作 |
MSG_ERR | 接收错误消息作为辅助数据 |
MSG_OOB | 如果协议支持,获取带外数据 |
MSG_PEEK | 返回数据包内容,而不真正取走数据包 |
MSG_TRUNC | 即使数据包被截断,也返回数据包的长度 |
MSG_WAIT | 等待直到所有数据可用 |
服务器例程
#include
#include
#include
#include
#include
#include
int main()
{
int sockfd;
int clitfd
struct sockaddr_in serv_addr;
struct sockaddr_in clint_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
char buffer[]="hello I'm a server"
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) //创建服务器套接字
{
fprintf(stderr,"socket failed\n");
exit(1);
}
serv_addr.sin_family=AF_INET; //初始化服务器IP,端口号
serv_addr.sin_addr.s_addr=htonl(127.0.0.1);
serv_addr.sin_port=htons(8080);
bind(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr)); //将服务器与地址绑定
listen(sockfd,10); //监听
clitfd=accept(sockfd,(struct sockaddr*)&clint_addr,(socklen_t)(sizeof(clint_addr))); //接受请求,返回客户端描述符
send(clitfd,buffer,sizeof(buffer),0); //向客户端写数据
close(sockfd); //关闭套接字描述符
close(clitfd);
return 0;
}
客户端例程
#include
#include
#include
#include
#include
#include
int main()
{
struct sockaddr_in clint_addr;
struct sockaddr_in serv_addr;
int clintfd;
char buffer[100];
memset(&clint_addr, 0, sizeof(clint_addr));
if((clintfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"socket failed\n");
exit(1);
}
clint_addr.sin_family=AF_INET;
clint_addr.sin_addr.s_addr = htonl(127.0.0.1);
clint_addr.sin_port = htons(1234);
if((connect(clintfd,(struct sockaddr_in*)&serv_addr,sizeof(serv_addr)))==-1)
{
fprintf(stderr,"connect failed\n");
exit(1);
}
recv(clintfd,buffer,sizeof(buffer),0)
close(clintfd);
return 0;
}