linux网络通信-套接字

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;
	
}

你可能感兴趣的:(linux)