欢迎来到.net学习网

欢迎联系站长一起更新本网站!QQ:879621940

您当前所在位置:首页 » ASP.Net » 正文

热门阅读

Socket编程(一)

创建时间:2011年12月10日 21:04  阅读次数:(6490)
分享到:
一、基本知识
主机字节序和网络字节序
主机字节序即内存中存储字节的方法有: 
1.Little endian:将低序字节存储在起始地址
2. Big endian:将高序字节存储在起始地址

网络字序表示网络协议在处理多字节时的顺序,一律为 big endian 

主机字节序和网络字节序转换的函数:
#include <netinet/in.h >
uint16_t htons(uint16_t <16 位的主机字节序 >)
uint32_t htonsl(uint32_t <32 位的主机字节序 >) //转换为网络字节序 
uint16_t ntohs(uint16_t <16 位的网络字节序 >)
uint32_t ntohl(uint32_t <32 位的网络字节序 >) //转换为主机字节序


缓冲区
每个 TCP SOCKET 有一个发送缓冲区和一个接收缓冲区,TCP 具有流量控制,所以接收缓冲区]的大小就是通知另一端的窗口的大小,对方不会发大于该窗口大小的数据;而 UDPSOCKET 只有一个接收缓冲区无流量控制,当接收的数据报溢出时就会被丢弃

通信域(地址族)
套接字存在于特定的通信域(即地址族)中,只有隶属于同一地址族的套接字才能建立对话。Linux 支持 AF_INET(IPv4 协议)、AF_INET6(IPv6 协议)和 AF_LOCAL(Unix域协议)。

套接口(socket)=网络地址+端口号。,要建立一个套接口必须调用 socket函数,套接口有三种类型,即字节流套接口(SOCK_STREAM),数据报套接口(SOCK_DGRAM)和原始套接口(SOCK_RAW)。定义一个连接的一个端点的两元组,即 IP 地址和端口号,称为一个套接口。 

在网络连接中,两个端点所组成的四元组(即本地 IP、本地 PORT、远程 IP 和远程 PORT)称为 socket pair,该四元组唯一的标识了一个网络连接。该情况可通过 netstat验证。

二、 socket 地址结构 
1.IPv4 的 Socket 地址结构(定长) 
Struct in_addr{
In_addr_t s_addr; // 32 位IP 地址,网络字节序
}
Struct sockaddr_in{
Uint8_t sin_len;//IPv4 为固定的16 字节长度
Sa_family_t sin_family; //地址簇类型,为 AF_INET
In_port_t sin_port; //16 位端口号,网络字节序
Struct in_addr sin_addr; // 32位IP 地址
Char sin_zero[8]; //未用


2.IPv6 的 socket 地址结构(定长)
struct in6_addr{
uint8_t s6_addr[16]; //128位 IP 地址,网络字节序
}
struct sockaddr_in6{
uint8_t sin6_len; //IPv6 为固定的24 字节长度
sa_family_t sin6_family; //地址簇类型,为 AF_INET6
in_port_t sin6_port; //16 位端口号,网络字节序
uint32_t sin6_flowinfo; //32 位流标签
struct in6_addr sin6_addr; //128位 IP 地址


3.UNIX 域 socket 地址结构(变长)
Struct sockaddr_un,地址簇类型为 AF_LOCAL 

4.数据链路 socket 地址结构(变长)
struct sockaddr_dl,地址簇类型为 AF_LINK

5.通用的 socket 地址结构
struct sockaddr{
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];


三、 C/S 网络编程
初始化sock连接符:
int socket(int domain, int type, int protocol); 
函数返回 socket描述符,返回-1表示出错
domain参数只能取AF_INET, protocol 参数一般取0

应用示例:
TCP方式:sockfd = socket(AF_INET,SOCK_STREAM,0);
UDP方式:sockfd =socket(AF_INET, SOCK_DGRAM,0);


绑定端口:
int bind(int sockfd, struct sockaddr *sa, int addrlen);

函数返回-1表示出错,最常见的错误是该端口已经被其他程序绑定。 

需要注意的一点:在Linux系统中,1024 以下的端口只有拥有 root 权限的程序才能绑定.

连接网络(用于 TCP 方式): 
int connect(int sockfd, struct sockaddr *servaddr, int addrlen);

函数返回-1表示出错,可能是连接超时或无法访问。返回0 表示连接成功,可以通过sockfd传输数据了。 

监听端口(用于 TCP 方式):
int listen(int sockfd, int queue_length); 

需要在此前调用 bind()函数将sockfd绑定到一个端口上,否则由系统指定一个随机的端口。

接收队列:一个新的 Client 的连接请求先被放在接收队列中,直到 Server程序调用 accept 函数接受连接请求。 
第二个参数queue_length,指的就是接收队列的长度 也就是在Server 程序调用 accept 函数之前最大允许的连接请求数,多余的连接请求将被拒绝。

响应连接请求(用于TCP 方式):
int accept(int sockfd,struct sockaddr *addr,int *addrlen);

accept()函数将响应连接请求,建立连接并产生一个新的 socket 描述符来描述该连接,该连接用来与特定的 Client 交换信息。 

函数返回新的连接的socket 描述符,错误返回-1
addr将在函数调用后被填入连接对方的地址信息,如对方的IP、端口等。
addrlen 作为参数表示 addr 内存区的大小,在函数返回后将被填入返回的 addr结构的大小。
accept缺省是阻塞函数,阻塞直到有连接请求

应用示例:
struct sockaddr_in their_addr; /* 用于存储连接对方的地址信息*/
int sin_size = sizeof(struct sockaddr_in);
… …(依次调用socket(), bind(), listen()等函数)
new_fd = accept(sockfd, &their_addr, &sin_size);
printf(”对方地址: %s\n", inet_ntoa(their_addr.sin_addr)); 
… …
 

关闭socket连接:
int close(int sockfd); 
关闭连接将中断对该socket 的读写操作。
关闭用于 listen()的socket 描述符将禁止其他 Client的连接请求。

部分关闭 socket连接:
int shutdown(int sockfd, int how);
Shutdown()函数可以单方面的中断连接,即禁止某个方向的信息传递。 
参数how的意思:
0 - 禁止接收信息
1 - 禁止发送信息
2 - 接收和发送都被禁止,与 close()函数效果相同

socket轮询选择:
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, structtimeval *timeout); 

应用于多路同步 I/O 模式(将在同步工作模式中详细讲解)
FD_ZERO(*set) 清空socket 集合
FD_SET(s, *set) 将s加入 socket 集合
FD_CLR(s, *set) 从socket 集合去掉 s
FD_ISSET(s, *set) 判断s是否在socket 集合中
常数FD_SETSIZE:集合元素的最多个数

等待选择机制:
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
是 select 机制的一个变种,应用于多路同步I/O模式(将在同步工作模式中详细讲解)
ufds是 pollfd 结构的数组,数组元素个数为nfds。
struct pollfd {
int fd; /* 文件描述字 */
short events; /* 请求事件集合 */
short revents; /* 返回时间集合 */
}; 


接收/发送消息:
TCP方式:
int send(int s, const void *buf, int len, int flags); 
int recv(int s, void *buf, int len, int flags); 


函数返回实际发送/接收的字节数,返回-1 表示出错,需要关闭此连接。
函数缺省是阻塞函数,直到发送/接收完毕或出错

注意:如果send函数返回值与参数 len不相等,则剩余的未发送信息需要再次发送 

UDP方式:
int sendto(int s, const void *buf, int len, int flags, const struct sockaddr *to, int tolen);
int recvfrom(int s,void *buf, int len, int flags, struct sockaddr *from, int *fromlen);

与 TCP 方式的区别:
需要指定发送/接收数据的对方(第五个参数to/from)
函数返回实际发送/接收的字节数,返回-1 表示出错。
函数缺省是阻塞函数,直到发送/接收完毕或出错。

注意:如果send函数返回值与参数 len不相等,则剩余的未发送信息需要再次发送
注意,网络字节流的读写不同于文件的读写,由于 socket 缓冲的因素,可能读写的字节数小于所指定的字节数。所以可以使用如下函数:
ssize_t readn(int fd, void * buf,size_t n)
{
ssize_t nleft;
ssize_t nread;
char *ptr;
ptr = buf;
nleft = n;
while (nleft  > 0)
{
if ((nread = read(fd, ptr, nleft)) < 0){
if (errno EINTR)
nread = 0;
else
return (-1);
}
else if (nread 0) //EOF
break;
nleft -= nread;
ptr += nread;
}
return (n - nleft);
}

ssize_t written(int fd,const void *buf,size_t n) 
{
ssize_t nleft;
ssize_t nwrite;
const char *ptr;
ptr = buf;
nleft = n;
while (nleft  > 0) {
if (nwrite = write(fd, ptr, left) <= 0) {
if (errno EINTR) {
nwrite = 0;
else
return (-1);
}
nleft -= nwrite;
ptr += nwrite;
}

return (n);


基于消息的方式:
int sendmsg(int s, const struct msghdr *msg, int flags);
int recvmsg(int s, struct msghdr *msg, int flags);


标志位:
上面这六个发送/接收函数均有一个参数 flags,用来指明数据发送/接收的标志,常用的标志主要有:
MSG_PEEK 对数据接收函数有效,表示读出网络数据后不清除已读的数据
MSG_WAITALL 对数据接收函数有效,表示一直执行直到 buf 读满、socket 出错或者程序收到信号。 
MSG_DONTWAIT 对数据发送函数有效,表示不阻塞等待数据发送完后返回,而是直接返回。
(只对非阻塞 socket 有效)
MSG_NOSIGNAL 对发送接收函数有效,表示在对方关闭连接后出错但不发送 SIGPIPE 信号给程序。
MSG_OOB 对发送接收都有效,表示读/写带外数据(out-of-band data)

IP 地址字符串和网络字节序的二进制 IP 地址相互转换的函数:
#inlcude <arpa/inet.h >
int inet_aton(const char * <IP 地址字符串 >, struct in_addr * <32 位的网络字节序形式的 IP 地址 >) 

成功—1
失败—0

[通用地址函数]int inet_pton(int <地址簇类型 >,可以是 AF_INET/AF_INET6 >,const char
* <IP 地址字符串 >,void * <32 位的网络字节序形式的 IP 地址 >) 
成功—1
格式错误—0
失败—0

in_addr_t inet_addr(const char * <IP 地址字符串 >)返回 32 位网络字节序的 IP 地址,失败—INADDR_NONE
char *inet_ntoa(struct in_addr <32 位的网络字节序形式的 IP 地址 >) 返回 IP 地址字符串 

const char * inet_ntop(int <地址簇类型 >, const void * <32 位的网络字节序形式的 IP地址 >, char * <IP 地址字符串 >, size_t <IP 地址字符串的最大长度 >) 

返回指向结果<IP 地址字符串 >的指针

字节顺序转换
htons()--"Host to Network Short"
htonl()--"Host to Network Long"
ntohs()--"Network to Host Short"
ntohl()--"Network to Host Long"

连接过程是通过一系列状态表示的,这些状态有:LISTEN,SYN-SENT,SYN-RECEIVED,ESTABLISHED,FIN-WAIT-1,FIN-WAIT-2,CLOSE-WAIT,CLOSING,LAST-ACK,TIME-WAIT和CLOSED。CLOSED 表示没有连接,各个状态的意义如下: 
LISTEN - 侦听来自远方 TCP 端口的连接请求;
SYN-SENT - 在发送连接请求后等待匹配的连接请求;
SYN-RECEIVED - 在收到和发送一个连接请求后等待对连接请求的确认;
ESTABLISHED - 代表一个打开的连接,数据可以传送给用户;
FIN-WAIT-1 - 等待远程TCP 的连接中断请求,或先前的连接中断请求的确认;
FIN-WAIT-2 - 从远程TCP 等待连接中断请求;
CLOSE-WAIT - 等待从本地用户发来的连接中断请求;
CLOSING - 等待远程 TCP 对连接中断的确认;
LAST-ACK - 等待原来发向远程 TCP的连接中断请求的确认;
TIME-WAIT - 等待足够的时间以确保远程 TCP 接收到连接中断请求的确认;
CLOSED - 没有任何连接状态;

一个 tcp 协议的 socket编程例子:
[Server]
/* 主函数 */
int listenfd, connfd;
struct sockaddr_in servaddr, cliaddr;
struct hostent *hp;
struct servent *sp;
struct in_addr **pptr;
if ((hp = gethostbyname(argv[1])) NULL)//error
if ((sp = getservbyname(argv[2], “tcp”)) NULL)//error
pptr = (struct in_addr **)hp- >h_addr_list;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;//servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 
memcpy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr));
servaddr.sin_port = htons(portnum);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, listenqueuenum);
signal(SIGCHLD,sig_chld);
for (;;)
{
len = sizeof(cliaddr);
if ((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &len)) < 0) {
if (errorno EINTR)
continue;
else
perror(“accept error”);
}
if (fork() 0) //子进程,复制父进程的所有描述字,所以 listenfd 和 connfd 被父子进程所共享,描述字的访问记数值都累计为2
{
close(listenfd);
printf(“connection from %s, port %d\n”, inet_ntop(AF_INET,&cliaddr,.sin_addr, buf, sizeof(buf)), ntohs(cliaddr.sin_port));
snprintf(buf, ……);
write(connfd, buf, strlen(buf));
… //process
close(connfd); //处理结束后关闭 socket 连接
exit(0); //子进程退出
}
close(connfd); //父进程用不到 connfd,可以将其关闭,由于描述字的访问记数值由2 减1,不会触发 FIN 分节,(和 shundown 不同)只有为 0 时,才真正关闭连接
}

自选端口应该大于1023(不要是保留端口),小于49152(临时端口)

本章内容来自于互联网,小编只负责排版,因受小编技术所限,有时断句的地方并不准确,但小编已经尽力让本章整理的更有可读性,请谅解。因小编保存这篇文章时间过长,已记不清来源,所以未标注来源,敬请谅解!

来源:
说明:所有来源为 .net学习网的文章均为原创,如有转载,请在转载处标注本页地址,谢谢!
【编辑:Wyf

打赏

取消

感谢您的支持,我会做的更好!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

最新评论

共有评论0条
  • 暂无任何评论,请留下您对本文章的看法,共同参入讨论!
发表评论:
留言人:
内  容:
请输入问题 97+52=? 的结果(结果是:149)
结  果: