本文共 11049 字,大约阅读时间需要 36 分钟。
需要掌握:
socket(),获取SOCKETbind(), 绑定SOCKET到本地地址inet_pton(), IP地址格式转换:点分式 --> 大整数recvfrom(),用于报式套接字,从 SOCKET上接收信息recv(), 用于流式套接字,从 SOCKET上接收信息inet_ntop(),IP地址格式转换:大整数格式 --> 点分式格式send(), 用于流式套接字 向SOCKET发送数据sendto(),用于报式套接字,向SOCKET发送数据
跨主机通信注意:
被动端(先运行,等待被访问)
1 取得SOCKET :socket()2 给SOCKET取得地址,即绑定本地地址(包含端口,ip等信息): bind()3 收/发消息 :recvfrom()4 关闭SOCKET: close()
主动端
1 取得SOCKET2 给SOCKET取得地址(可省略)3 发/收消息:sendto()4 关闭SOCKET
为什么 主动端的 bind()即绑定本地地址操作 可以省略呢?
bind() 是给SOCKET取得地址,即绑定本地地址。这个操作是和本机的约定。
发送端如果不和本机约定地址,即省略bind()操作。当前SOCKET建立成功后。系统会为我们分配一个可用的空闲的地址给我们用,在进程结束之前 该端口一直给我们用。
socket()
NAME创建通信端点,取得SOCKET socket - create an endpoint for communicationSYNOPSIS #include <sys/types.h> /* See NOTES */ #include <sys/socket.h>/* 用协议族domain 中的某个协议protocol 来完成type类型的传输注意:如果目标协议族中有一个或多个协议可以支持 目标传输类型的话,那么协议protocol 可以写0,表示用协议族中默认支持目标传输类型的协议 如用 :soccket(AF_INET,SOCK_DGRAM,0); 表示用协议族 AF_INET协议族 默认支持报式套接字的协议 来完成 报式SOCK_DGRAM套接字传输,domain : 域,如协议族type :上层怎么实现protocol:协议*/ int socket(int domain, int type, int protocol);
RETURN VALUE 返回值为文件描述符
On success, a file descriptor for the new socket is returned. On error, -1 is returned, and errno is set appropriately.
domain : Name Purpose Man page AF_UNIX, AF_LOCAL Local communication 本地协议 unix(7) AF_INET IPv4 Internet protocols ip(7) AF_INET6 IPv6 Internet protocols ipv6(7) AF_IPX IPX - Novell protocols AF_NETLINK Kernel user interface device netlink(7) AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7) AF_AX25 Amateur radio AX.25 protocol 无线电 短波通信 AF_ATMPVC Access to raw ATM PVCs AF_APPLETALK AppleTalk ddp(7) AF_PACKET Low level packet interface packet(7) AF_ALG Interface to kernel crypto APItype:SOCK_STREAM 流式套接字,Provides sequenced, reliable, two-way, connection-based byte streams.有序可靠:只要接收方能够接到数据,就可以保证 当前数据包中的内容是正确的。不能保证不丢包,网络传输中会有丢包双工基于连接:点对点,一对一字节流传输 SOCK_DGRAM 报式数据分组无连接的不可靠的SOCK_SEQPACKET 有序可靠的报式有序可靠的报式...
bind()
NAME给SOCKET绑定一个地址 bind - bind a name to a socketSYNOPSIS #include <sys/types.h> /* See NOTES */ #include <sys/socket.h>/*sockfd: 获取 SOCKET 返回的文件描述符addr :协议地址,依赖于当前用到的协议族中的地址信息,AF_INET 协议族中的 协议地址类型为 struct sockaddr_in addrlen :协议地址空间大小*/ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);sockaddr 依赖于当前用到的协议族中的地址信息 The sockaddr structure is defined as something like: struct sockaddr { sa_family_t sa_family; char sa_data[14]; }
不同的协议族 来 绑定自己这端的地址 所用的结构体是不一样的。所以是不存在 struct sockaddr 类型的。所以我们的处理方式是:我们用的是哪一个协议族,就把该协议族地址作为addr ,然后再把地址长度写到addrlen
AF_INET see ip(7)
man 7 ip
/*
注意 :IP地址和端口,是需跟着网络一起发送的。代表自己的身份
*/
struct sockaddr_in { //协议族 address family: AF_INET sa_family_t sin_family; //需要的端口 in_port_t sin_port; //IP地址 并非点分式,而是大整数internet address ,用的时候需要格式转换:inet_pton() struct in_addr sin_addr; }; /* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */ };
所以 AF_INET 协议族中的 协议地址类型为 struct sockaddr_in
RETURN VALUE
On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
inet_pton() IP地址格式转换:点分式 --> 大整数
NAME将 IPv4 and IPv6 addresses 转换为 二进制格式,即将点分式 转换为 二进制格式 inet_pton - convert IPv4 and IPv6 addresses from text to binary formSYNOPSIS #include <arpa/inet.h>/*af : 协议族,只能是 AF_INET or AF_INET6,即IPV4或者IPV6src:IP地址dst :转换之后的存储空间*/ int inet_pton(int af, const char *src, void *dst);
0.0.0.0:能够匹配任何的IP地址,表示在当前绑定这个阶段,我们自己的IP地址是多少,0.0.0.0 就会替换成我们当前的IP地址
recvfrom() 从 SOCKET上接收信息
NAME从 SOCKET上接收信息 recv, recvfrom, recvmsg - receive a message from a socketSYNOPSIS #include <sys/types.h> #include <sys/socket.h>/* 用于流式套接字sockfd :目标SOCKETbuf :接收信息存储地址len:接收信息存储空间 大小 flags :特殊要求*/ ssize_t recv(int sockfd, void *buf, size_t len, int flags);/* recvfrom 用于报式套接字sockfd :目标SOCKETbuf :接收信息存储地址len:接收信息存储空间 大小 flags :特殊要求src_addr : 用于保存发送端的地址,即保存对端地址,接收到消息的时候保存addrlen :发送端地址空间大小,即对端地址代表空间的大小*/ ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
recv()应用于流式套接字,只需要指定目标SOCKET,和存储信息的位置即大小,以及有无特殊要求即可。因为是提前建立好链接的,一对一,点对点的连接方式,所以不用记录 对端是谁。
recvfrom() 用于报式套接字通信,接收的每一个消息来源可能不一致,所以除了需要指定目标SOCKET,和存储信息的位置即大小,以及有无特殊要求之外。还需要记录对方是谁,即发送端身份。
inet_ntop() 大整数格式 --> 点分式格式
NAME 转换IPv4 and IPv6 addresses 地址格式, 大整数格式 --> 点分式格式 inet_ntop - convert IPv4 and IPv6 addresses from binary to text formSYNOPSIS #include <arpa/inet.h>/*af: 协议族src :待转换的IP地址dst: 将目标IP 准换到 该地址size:dst指向的空间大小*/ const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
send()、sendto()
NAME send, sendto, sendmsg - send a message on a socketSYNOPSIS #include <sys/types.h> #include <sys/socket.h>/* 用于流式套接字 发送数据sockfd:目标SOCKETbuf :发送数据信息存储地址len:发送数据信息存储空间 大小 */ ssize_t send(int sockfd, const void *buf, size_t len, int flags);/* 用于报式套接字 发送数据sockfd:目标SOCKETbuf :发送数据信息存储地址len:发送数据信息存储空间 大小 flags :特殊要求dest_addr : 接收端的地址,即对端地址addrlen :接收端地址空间大小*/ ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
RETURN VALUE
On success, these calls return the number of bytes sent. On error, -1 is returned, and errno is set appropriately.
实验 :报式套接字传输数据
proto.h
#ifndef PROTO_H_#define PROTO_H_#define RCVPORT "1989"#define NAMESIZE 11#define IPSTRSIZE 40struct msg_st{ char name[NAMESIZE]; uint32_t math; uint32_t chinese;}__attribute__((pack));//UDP数据包 结构体一定不能考虑对齐,因为每个平台的上面的对齐方式可能会有差异,所以告诉gcc,该结构体 不需要对齐。
#endif
rcver.c 接收端
#include <stdlib.h>#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <string.h>#include "proto.h"int main(){ int sd; struct sockaddr_in laddr,raddr; struct msg_st rbuf; socklen_t raddr_len; char ipstr[IPSTRSIZE];//取得SOCKET, 用 AF_INET协议族中 默认支持报式套接字的协议 来完成 报式SOCK_DGRAM套接字传输, sd = socket(AF_INET,SOCK_DGRAM,0);//IPPROTO_UDP if(sd < 0) { perror("soccket()"); exit(1); }//设置 AF_INET 协议族地址信息结构体,AF_INET 协议族中的 协议地址类型为 struct sockaddr_in // 协议族为 AF_INET laddr.sin_family = AF_INET;// 设置端口为1989,因为需要将自己的地址信息(包括端口信息)发出去,所以需要注意字节序问题,即 从主机发向网络,htons laddr.sin_port = htons(atoi(RCVPORT)); inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);//0.0.0.0:any address// 给SOCKET绑定一个地址,关联到目标协议族地址信息结构体 if(bind(sd,(void *)&laddr,sizeof(laddr)) < 0) { perror("bind"); exit(1); } /* !!!! *///一定要注意初始化对端地址空间大小信息 raddr_len = sizeof(raddr); while(1) {//以报式套接字方式 从 SOCKET上接收信息,raddr用于保存发送端的地址,//即保存对端地址,接收到消息的时候保存,remote端地址信息。接收到的信息存储到rbuf recvfrom(sd,&rbuf,sizeof(rbuf),0,(void *)&raddr,&raddr_len); // 转换IPv4 and IPv6 addresses 地址格式, 大整数格式 --> 点分式格式 存储到ipstr inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);// raddr.sin_port) 是从socket接收到的信息,不是单字节信息,需要字节序转换 ntohs printf("---MESSAGE FROM %s:%d---\n",ipstr,ntohs(raddr.sin_port)); printf("---NAME = %s\n",rbuf.name);//单字节信息,不需要字节序转换 printf("---MATH = %d\n",ntohl(rbuf.math));//需要字节序转换 printf("---CHINESE = %d\n",ntohl(rbuf.chinese));//需要字节序转换 } close(sd); exit(0);}
发送端:
#include <stdlib.h>#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <string.h>#include <unistd.h>#include "proto.h"int main(int argc,char *argv[]){ int sd; struct sockaddr_in raddr; struct msg_st sbuf; if(argc < 2) { fprintf(stderr,"Usage ...\n"); exit(1); } sd = socket(AF_INET,SOCK_DGRAM,0);//IPPROTO_UDP if(sd < 0) { perror("soccket()"); exit(1); } //bind() strcpy(sbuf.name,"MHR"); sbuf.math = htonl(rand()%100); sbuf.chinese = htonl(rand()%100); raddr.sin_family = AF_INET; raddr.sin_port = htons(atoi(RCVPORT)); inet_pton(AF_INET,argv[1],&raddr.sin_addr); if(sendto(sd,&sbuf,sizeof(sbuf),0,(void *)&raddr,sizeof(raddr)) < 0) { perror("sendto()"); exit(1); } puts("OK"); close(sd); exit(0);}
实验结果可以看到,数据传输成功。并且 由于主动端没有bind() 实验中的几次传输 系统给主动端分配到端口分别是 45499,56709, 51977。被动端 打印了 主动端的IP 端口 数据信息等 信息。
查看网络状态
netstat -anu //u:udp 报式套接字netstat -ant // t:tcp 流式套接字mhr@ubuntu:~/Desktop/xitongbiancheng/socket/1$ mhr@ubuntu:~/Desktop/xitongbiancheng/socket/1$ netstat -anuActive Internet connections (servers and established)Proto Recv-Q Send-Q Local Address Foreign Address State udp 0 0 0.0.0.0:1989 0.0.0.0:* //可以看到 1989端口 已经打开 udp 0 0 127.0.1.1:53 0.0.0.0:* udp 0 0 0.0.0.0:68 0.0.0.0:* udp 0 0 0.0.0.0:631 0.0.0.0:* udp 0 0 0.0.0.0:51842 0.0.0.0:* udp 0 0 0.0.0.0:5353 0.0.0.0:* udp6 0 0 :::59961 :::* udp6 0 0 :::5353 :::* mhr@ubuntu:~/Desktop/xitongbiancheng/socket/1$ netstat -nap |grep address number 查看程序运行的pid如:mhr@ubuntu:~/Desktop/xitongbiancheng/socket/1$ netstat -nap |grep 0.0.0.0 //代码中被动端地址 是0.0.0.0(Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.)tcp 0 0 127.0.1.1:53 0.0.0.0:* LISTEN - udp 0 0 0.0.0.0:1989 0.0.0.0:* 2739/a.out //进程IP 2739 udp 0 0 127.0.1.1:53 0.0.0.0:* - udp 0 0 0.0.0.0:68 0.0.0.0:* - udp 0 0 0.0.0.0:631 0.0.0.0:* - udp 0 0 0.0.0.0:51842 0.0.0.0:* - udp 0 0 0.0.0.0:5353 0.0.0.0:* - mhr@ubuntu:~/Desktop/xitongbiancheng/socket/1$ kill 2739mhr@ubuntu:~/Desktop/xitongbiancheng/socket/1$ netstat -nap |grep 0.0.0.0(Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.)tcp 0 0 127.0.1.1:53 0.0.0.0:* LISTEN - udp 0 0 127.0.1.1:53 0.0.0.0:* - udp 0 0 0.0.0.0:68 0.0.0.0:* - udp 0 0 0.0.0.0:631 0.0.0.0:* - udp 0 0 0.0.0.0:51842 0.0.0.0:* - udp 0 0 0.0.0.0:5353 0.0.0.0:* - mhr@ubuntu:~/Desktop/xitongbiancheng/socket/1$
转载地址:http://hxme.baihongyu.com/