unix学习笔记------套接字-----王保明老师的笔记

来源:互联网 发布:网络平台招商加盟 编辑:程序博客网 时间:2024/05/17 02:38


linux Socket-应用编程-专题讲座 
written by 王保明
Socket编程基本实践
1Socket Api基本概念
 
什么是socket?
socket可以看成是用户进程与内核网络协议栈的编程接口。
socket不仅可以用于本机的进程间通信,还可以用于网络上不同主机的进程间通信。
    
tcp/ip通讯模型
 


    
IPv4套接口地址结构
IPv4套接口地址结构通常也称为“网际套接字地址结构”,它以“sockaddr_in”命名,定义在头文件<netinet/in.h>中
struct sockaddr_in {
uint8_t  sin_len; 4
sa_family_t  sin_family; 4
in_port_t sin_port; 2
struct in_addr sin_addr; 4
char sin_zero[8]; 8
}; 
sin_len:整个sockaddr_in结构体的长度,在4.3BSD-Reno版本之前的第一个成员是sin_family.
sin_family:指定该地址家族,在这里必须设为AF_INET
sin_port:端口
sin_addr:IPv4的地址;
sin_zero:暂不使用,一般将其设置为0   
通用地址结构
通用地址结构用来指定与套接字关联的地址。 
struct sockaddr {
uint8_t  sin_len;
sa_family_t  sin_family;
char sa_data[14]; //14
}; 
sin_len:整个sockaddr结构体的长度
sin_family:指定该地址家族
sa_data:由sin_family决定它的形式。   
网络字节序
字节序
大端字节序(Big Endian)
最高有效位(MSB:Most Significant Bit)存储于最低内存地址处,最低有效位(LSB:Lowest Significant Bit)存储于最高内存地址处。
小端字节序(Little Endian)
最高有效位(MSB:Most Significant Bit)存储于最高内存地址    处,最低有效位(LSB:Lowest Significant Bit)存储于最低内存地址处。
主机字节序
不同的主机有不同的字节序,如x86为小端字节序,Motorola 6800为大端字节序,ARM字节序是可配置的。
网络字节序
网络字节序规定为大端字节序   
字节序转换函数
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
说明:在上述的函数中,h代表host;n代表network s代表short;l代表long
  
地址转换函数
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);   
套接字类型
流式套接字(SOCK_STREAM)
提供面向连接的、可靠的数据传输服务,数据无差错,无重复的发送,且按发送顺序接收。
数据报式套接字(SOCK_DGRAM)
提供无连接服务。不提供无错保证,数据可能丢失或重复,并且接收顺序混乱。
原始套接字(SOCK_RAW)   
  
 
2SocketApi基本编程模型
 
TCP客户/服务器模型 
 


 


 
简单服务器模型
    
 
3Socket Api基本实践
Socket API基本用法
 
socket函数
包含头文件<sys/socket.h>
功能:创建一个套接字用于通信
原型
int socket(int domain, int type, int protocol);
参数
domain :指定通信协议族(protocol family)
type:指定socket类型,流式套接字SOCK_STREAM,数据报套接字SOCK_DGRAM,原始套接字SOCK_RAW
protocol :协议类型
返回值:成功返回非负整数, 它与文件描述符类似,我们把它称为套接口描述字,简称套接字。失败返回-1
  
bind函数
包含头文件<sys/socket.h>
功能:绑定一个本地地址到套接字
原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
sockfd:socket函数返回的套接字
addr:要绑定的地址
addrlen:地址长度
返回值:成功返回0,失败返回-1
  
listen函数
一般来说,listen函数应该在调用socket和bind函数之后,调用函数accept之前调用。
对于给定的监听套接口,内核要维护两个队列:
1、已由客户发出并到达服务器,服务器正在等待完成相应的TCP三路握手过程
2、已完成连接的队列


 


 
  
accept函数
包含头文件<sys/socket.h>
功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞。
原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数
sockfd:服务器套接字
addr:将返回对等方的套接字地址
addrlen:返回对等方的套接字地址长度
返回值:成功返回非负整数,失败返回-1


connect函数
包含头文件<sys/socket.h>
功能:建立一个连接至addr所指定的套接字
原型
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数
sockfd:未连接套接字
addr:要连接的套接字地址
addrlen:第二个参数addr长度
返回值:成功返回0,失败返回-1
 
Socket API 中的地址复用
 
SO_REUSEADDR
服务器端尽可能使用SO_REUSEADDR
在绑定之前尽可能调用setsockopt来设置SO_REUSEADDR套接字选项。
使用SO_REUSEADDR选项可以使得不必等待TIME_WAIT状态消失就可以重启服务器
 
Socket服务支持多并发(多客户端连接)  
 
分析最基本socket服务器客户端模型能否支持多客户端连接
 
 


    
    
 
点对点聊天程序设计与实现
 
点对点聊天程序,功能说明
点对点聊天程序,设计思想
    
 
4Socket Api编程进价
1流协议与粘包 
 
流协议与粘包
    
粘包产生的原因 
 
  
说明
tcp 字节流 无边界
udp 消息、数据报 有边界
对等方,一次读操作,不能保证完全把消息读完。 
对方接受数据包的个数是不确定的。   
产生粘包问题的原因
1、SQ_SNDBUF 套接字本身有缓冲区 (发送缓冲区、接受缓冲区)
2、tcp传送的端 mss大小限制
3、链路层也有MTU大小限制,如果数据包大于>MTU要在IP层进行分片,导致消息分割。
4、tcp的流量控制和拥塞控制,也可能导致粘包
5、tcp延迟发送机制 等等
结论:tcp/ip协议,在传输层没有处理粘包问题。   
粘包解决方案   
本质上是要在应用层维护消息与消息的边界
定长包
包尾加\r\n(ftp)
包头加上包体长度
更复杂的应用层协议   
编程实践
readn
writen   
 
2包头加上包体长度编程实践
 
包头加上包体长度
发报文时,前四个字节长度(转成网络字节序)+包体
收报文时,先读前四个字节,求出长度;根据长度读数据。   
 


3包尾加上\n编程实践
 
\n作为协议的边界
ssize_t recv(int s, void *buf, size_t len, int flags);
与read相比,只能用于套接字文件描述符;
多了一个flags
       MSG_OOB
   This  flag requests receipt of out-of-band data that would not be received in the normal data stream.  Some protocols place expedited data at thehead of the normal data queue, and thus this flag cannot be used with such protocols.
带外数据 紧急指针
       MSG_PEEK
This flag causes the receive operation to return data from the beginning of the receive queue without removing that data from the queue.  Thus, asubsequent receive call will return the same data.
可以读数据,不从缓存区中读走,利用此特点可以方便的实现按行读取数据。 
一个一个字符的读,方法不好;多次调用系统调用read方法  
  
 




4获取本机ip相关函数
getsockname & getpeername 
 
获取本地的地址(注意是已连接以后的套接字)
NAME
       getsockname - get socket name


SYNOPSIS
       #include <sys/socket.h>


       int getsockname(int s, struct sockaddr *name, socklen_t *namelen);  
struct sockaddr_in localaddr;
socklen_t addrlen = sizeof(localaddr);
//获取本地的地址
if (getsockname(sock, (struct sockaddr*)&localaddr, &addrlen) < 0)
ERR_EXIT("getsockname");


printf("ip=%s port=%d\n", inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port));
  
 


gethostname、gethostbyname、gethostbyaddr
 
gethostname、gethostbyname、gethostbyaddr
  


       The  domain  name  queries  carried out by gethostbyname() and gethostbyaddr() use a combination of any or all of the name server named(8), a broken out
       line from /etc/hosts, and the Network Information Service (NIS or YP), depending upon the contents of the order line  in  /etc/host.conf.   The  default
       action is to query named(8), followed by /etc/hosts.


       The hostent structure is defined in <netdb.h> as follows:


              struct hostent {
                      char    *h_name;        /* official name of host */
                      char    **h_aliases;    /* alias list */
                      int     h_addrtype;     /* host address type */
                      int     h_length;       /* length of address */
                      char    **h_addr_list;  /* list of addresses */
              }
              #define h_addr  h_addr_list[0]  /* for backward compatibility */


       The members of the hostent structure are:   
int getlocalip(char *ip)
{
char host[100] = {0};
    if (gethostname(host, sizeof(host)) < 0)
return -1;
struct hostent *hp;
    if ((hp = gethostbyname(host)) == NULL)
return -1;


strcpy(ip, inet_ntoa(*(struct in_addr*)hp->h_addr));
return 0;


}


int main(void)
{
char host[100] = {0};
if (gethostname(host, sizeof(host)) < 0)
ERR_EXIT("gethostname");

printf("\n\n\nhost:%s \n", host);


struct hostent *hp;
if ((hp = gethostbyname(host)) == NULL)
ERR_EXIT("gethostbyname");


int i = 0;
while (hp->h_addr_list[i] != NULL)
{
//char *inet_ntoa(struct in_addr in); 要求填入一个结构体元素
printf("%s\n", inet_ntoa(*(struct in_addr*)hp->h_addr_list[i]));
i++;
}

char ip[16] = {0};
getlocalip(ip);
printf("localip=%s\n", ip);
return 0;
}  
0 0
原创粉丝点击