socket网络编程
来源:互联网 发布:深圳网络推广qebang 编辑:程序博客网 时间:2024/06/08 13:49
1. 两个简单的例子
1.1. TCP 协议通信
客户端程序
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <signal.h>#include <stdio.h>#include <string.h>#include <netinet/in.h> #include <unistd.h>#define SERVER_PORT 12345int main(int argc, char *argv[]){ int iSocketFd; int iError = 0; struct sockaddr_in tServerAddr; int iSockLen; unsigned char ucSendBuf[1024]; int iSendBufLen = 0; if(argc != 2){ printf("exe <ipaddr>\n"); return -1; } iSocketFd = socket(AF_INET, SOCK_STREAM, 0); /* 建立一个 socket */ if(-1 == iSocketFd){ printf("build socket error\n"); return -1; } tServerAddr.sin_family = AF_INET; /* 属于IPv4 */ tServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */ inet_aton(argv[1], (struct in_addr *)&tServerAddr.sin_addr); iError = connect(iSocketFd, (const struct sockaddr *)&tServerAddr, sizeof(struct sockaddr)); /* 对指定的目标主机进行连接 */ if(iError){ printf("failed to connect\n"); return -1; } while(1){ scanf("%s", ucSendBuf); iError = send(iSocketFd, ucSendBuf, sizeof(ucSendBuf), 0); /* 发送数据 */ if(iError <= 0){ printf("Send buffer error\n"); return -1; } } return 0;}
服务器端
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <signal.h>#include <stdio.h>#include <netinet/in.h> #include <unistd.h>#define LISTEN_PORT 12345#define MAX_CONNECT 5int main(int argc, char *argv[]){ int iSocketServerFd; int iSocketclientFd; int iError = 0; struct sockaddr_in tServerAddr; struct sockaddr_in tClientAddr; int iSockLen; int iClientIdentify = 0; unsigned char ucRecvBuf[1024]; int iRecvBufLen = 0; /* * SOCK_STREAM : Provides sequenced, reliable, two-way, connection-based byte streams * 连续的,可靠的,双向的,字节流 */ iSocketServerFd = socket(AF_INET, SOCK_STREAM, 0); /* 建立一个 socket */ if(-1 == iSocketServerFd){ printf("build socket error\n"); return -1; } tServerAddr.sin_family = AF_INET; /* 属于IPv4 */ tServerAddr.sin_port = htons(LISTEN_PORT); /* host to net, short */ tServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* 监控所有的ip INADDR_ANY = 0.0.0.0*/ /* 绑定地址到socket */ iError = bind(iSocketServerFd, (const struct sockaddr *)&tServerAddr, sizeof(struct sockaddr)); /* 绑定 socket 与 IP */ iError |= listen(iSocketServerFd, MAX_CONNECT); /* 监听,最多MAX_CONNECT个 */ if(iError){ printf("failed to listen\n"); return -1; } /* 防止由于子进程退出产生僵死进程 */ signal(SIGCHLD,SIG_IGN); while(1){ iSockLen = sizeof(struct sockaddr); /* 接受连接,此函数会阻塞等待,直到有连接 */ iSocketclientFd = accept(iSocketServerFd, (struct sockaddr *)&tClientAddr, &iSockLen); if(-1 != iSocketclientFd){ printf("connect from %s, connect %d\n", (char *)inet_ntoa(tClientAddr.sin_addr.s_addr), iClientIdentify++); if(!fork()){ /* 创建子线程,!fork() 表明是主进程 */ while(1){ /* 接收数据,会阻塞 */ iRecvBufLen = recv(iSocketclientFd, ucRecvBuf, sizeof(ucRecvBuf), 0); if(iRecvBufLen > 0){ ucRecvBuf[iRecvBufLen] = '\0'; printf("Receive buf %s, form %s\n", ucRecvBuf, (char *)inet_ntoa(tClientAddr.sin_addr.s_addr)); }else{ close(iSocketclientFd); return -1; } } } } } close(iSocketServerFd); return 0;}
测试
首先启动服务器端,然后根据客户端的使用提示进行连接,在客户端输入字符之后回车能够看到在服务器端接收到来自客户端的数据
- client 端执行
./client 192.168.2.102- 立马可以在服务器端看到
connect from 192.168.2.102, connect 0- 客户端输入
hellow world 回车- 服务器端可以看到
Receive buf hellow, form 192.168.2.102Receive buf world, form 192.168.2.102
1.2 UDP
客户端
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <signal.h>#include <stdio.h>#include <string.h>#include <netinet/in.h> #include <unistd.h>#define SERVER_PORT 12345int main(int argc, char *argv[]){ int iSocketFd; int iError = 0; struct sockaddr_in tServerAddr; int iSockLen; unsigned char ucSendBuf[1024]; int iSendBufLen = 0; if(argc != 2){ printf("exe <ipaddr>\n"); return -1; } iSocketFd = socket(AF_INET, SOCK_DGRAM, 0); if(-1 == iSocketFd){ printf("build socket error\n"); return -1; } tServerAddr.sin_family = AF_INET; /* 属于IPv4 */ tServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */ if(0 == inet_aton(argv[1], (struct in_addr *)&tServerAddr.sin_addr)){ printf("Wrong addr\n"); return -1; } while(1){ scanf("%s", ucSendBuf); iSendBufLen = sendto(iSocketFd, ucSendBuf, sizeof(ucSendBuf), 0, (const struct sockaddr *)&tServerAddr, sizeof(struct sockaddr)); if(iSendBufLen <= 0){ printf("Send buffer error\n"); return -1; } } return 0;}
服务器端
#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <signal.h>#include <stdio.h>#include <netinet/in.h> #include <unistd.h>#define LISTEN_PORT 12345int main(int argc, char *argv[]){ int iSocketServerFd; int iSocketclientFd; int iError = 0; struct sockaddr_in tServerAddr; struct sockaddr_in tClientAddr; int iSockLen; int iClientIdentify = 0; unsigned char ucRecvBuf[1024]; int iRecvBufLen = 0; /* * SOCK_STREAM : Provides sequenced, reliable, two-way, connection-based byte streams * 连续的,可靠的,双向的,字节流 */ iSocketServerFd = socket(AF_INET, SOCK_DGRAM, 0); if(-1 == iSocketServerFd){ printf("build socket error\n"); return -1; } tServerAddr.sin_family = AF_INET; /* 属于IPv4 */ tServerAddr.sin_port = htons(LISTEN_PORT); /* host to net, short */ tServerAddr.sin_addr.s_addr = INADDR_ANY; /* 监控所有的ip INADDR_ANY = 0.0.0.0*/ /* 绑定地址到socket */ iError = bind(iSocketServerFd, (const struct sockaddr *)&tServerAddr, sizeof(struct sockaddr)); if(-1 == iError){ printf("Bind error\n"); return -1; } while(1){ iSockLen = sizeof(struct sockaddr); iRecvBufLen = recvfrom(iSocketServerFd, ucRecvBuf, sizeof(ucRecvBuf), 0, (struct sockaddr *)&tClientAddr, &iSockLen); if(iRecvBufLen > 0){ printf("%s : %s\n", (char *)inet_ntoa(tClientAddr.sin_addr), ucRecvBuf); } } close(iSocketServerFd); return 0;}
测试:方法同上面一样
两者的区别:
- UDP 不需要进行连接,直接朝目标发送数据即可,UDP 是用户数据报,无连接的
- TCP 需要先进行连接,之后才能够发送数据,TCP 是传输控制协议,需要连接,比 UDP 更加可靠,因为 TCP 有重传机制,而 UDP 协议只是将数据传输过去,并不关心对方是否接收到了数据
1.3 步骤
服务器端
- 获得一个套接字描述符
- 设置地址信息
- 绑定套接字描述符到地址信息结构体
- 监听套接字
- 等待接受连接,获得连接方的套接字描述符
- 连接成功之后,接收或者发送字符
客户端
- 获得一个套接字描述符
- 设置地址信息
- 连接
- 连接成功之后选择接收或者发送数据
2. 套接字
套接字是通信端点的抽象,可以类比看作是文件,文件可以存储内容,从套接字里面我们也可以读取到来自其他地方发送到该端点的数据,如同使用文件描述符(句柄)访问文件一样,套接字描述符用来进行套接字的访问
int socket(int domain, int type, int protocol); /* 获得一个套接字描述符 */domain AF_INET IPv4 因特网域 AF_INET6 IPv6 因特网域 AF_UNIX UNIX 域 AF_LOCAL AF_UNIX 别名 AF_UPSPEC 未指定,可用于所有的域type SOCK_DGRAM 固定长度的、无连接的、不可靠的报文传输 SOCK_RAW IP 协议的数据报接口 SOCK_SEQPACKET 固定长度的、有序的、可靠的、面向连接的报文传递 SOCK_STREAM 有序的、可靠的、双向的、面向连接的字节流protocol IPPROTO_IP IPv4 网际协议 IPPROTO_IPV6 IPv6 网际协议 IPPROTO_ICMP 因特网控制报文协议 IPPROTO_RAW 原始 IP 数据报协议 IPPROTO_TCP 传输控制协议 IPPROTO_UDP 用户数据报协议
一般来说 protocol 保持 0,意思是保持默认值,对于 SOCK_STREAM 来说,其默认的 protocol 是 TCP 协议;对于 SOCK_DGRAM 来说,其默认的协议是 UDP 协议
- UDP:协议包里面包涵数据的次序,数据的目的地址。因为这是无连接的,不可靠的协议,所以当数据包被发送出去的时候,并不知道哪一个包会先行到达,并且有可能会丢失,每一包数据都会有目的地址
- TCP:事先建立一个连接,数据如果有丢失的话会有重传机制,包里面不含有特定的数据地址,因为已经建立了点对点的虚拟连接链路
close 函数可以关闭打开的 socket,还有另外一种关闭方式int shutdown(int sockfd, int how);sockfd 套接字描述符how SHUT_RD 关闭读取端 在上面例子的服务器端里面就是读取端,要在服务器端执行 SHUT_WR 关闭写入端 在上面例子的客户端里面就是写入端,要在客户端执行 SHUT_RDWR 关闭读写端 双面都可以执行,执行过后都可以到达效果shutdown 的用途就是在套接字描述符被多个地方引用的时候使用,如果使用 close 函数,要等到所有的引用都关闭之后才会释放这个套接字,而 shutdown 可以立即使某个套接字处于不活动状态
3. 地址格式
地址格式在不同的系统中有不同的定义方式,在 linux 中
struct socketaddr{ sa_family_t sa_family; /* 地址类 */ char sa_data[14]; /* 可变长度的地址 */}struct in_addr{ in_addr_t s_addr; /* IPv4 的地址 */}struct socketaddr_in{ sa_family_t sin_family; /* 地址类,16字节 */ in_port_t sin_port; /* 端口号,16字节 */ struct in_addr sin_addr; /* IPv4 的地址,32字节 */ unsigned char sin_zero[8]; /* 填充字段,用不到 */}
上面两者的长度都是 16 字节
由于各种系统不同的字节序,有的是大端,有的是小端,但是网络字节序是确定的,所以系统提供了统一的函数进行地址到字符串或者字符串到地址的转换
- 网络地址与主机地址转换
host to net longuint32_t htonl(uint32 hostint32); 返回以网络字节序表示的 32 位整数uint16_t htons(uint16 hostint16); 返回以网络字节序表示的 16 位整数uint32_t ntohl(uint32_t netint32); 返回以主机字节序表示的 32 位整数uint16_t ntohs(uint16_t netint16); 返回以主机字节序表示的 16 位整数
- IP 地址 . 分十进制与字符串之间的转换
in_addr_t inet_addr(const char *cp); 字符串到点分十进制char *inet_ntoa(struct in_addr in); 点分十进制到字符串int inet_aton(const char *cp, struct in_addr *inp); 字符串到点分十进制上面的函数仅支持 IPv4
- 得到主机的信息
/* node 主机名,可以是节点名或者是点分格式的主机地址 * service 服务名,如 http,nfs,tcp * hints 过滤器,不过滤的字段设置为 0 * res 返回的地址信息,是一个链表结构成员,需要用 freeaddrinfo 释放 */int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);void freeaddrinfo(struct addrinfo *res);struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; size_t ai_addrlen; struct sockaddr *ai_addr; char *ai_canonname; struct addrinfo *ai_next;};
- 将套接字与地址关联
sockaddr 一般是用 sockaddr_in 结构体代替,然后再强制转换过去int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);使用限制- 在进程正在运行的计算机上,指定的地址必须有效,不能指定一个其它机器的地址- 地址必须和创建套接字时候的地址族所支持的格式匹配- 地址端口号必须大于 1024,否则需要权限- 一般只能将一个套接字绑定到一个给定的地址上面,尽管有些协议允许多重绑定,例如 TCP 只能绑定一个主机到同一个端口,即使 IP 不同,主机相同也不行
- 建立连接
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
指定的地址是我们想与之通信的服务器地址。如果 sockfd 没有绑定到一个地址,connect 会给调用者绑定一个默认的地址
客户端
tGetSockAddr.sin_family = AF_INET; /* 属于IPv4 */tGetSockAddr.sin_port = htons(SERVER_PORT); /* host to net, short */memset(tGetSockAddr.sin_zero, 0, 8);inet_aton("192.168.2.100", (struct in_addr *)&tGetSockAddr.sin_addr);iError = bind(iSocketFd, (const struct sockaddr *)&tGetSockAddr, sizeof(struct sockaddr));if(iError){ printf("Bind error\n");}printf("tGetSockAddr.sin_family = %d\n", (int)tGetSockAddr.sin_family);printf("tGetSockAddr.sin_port = %d\n", ntohs(tGetSockAddr.sin_port));printf("tGetSockAddr.sin_addr = %s\n", (char *)inet_ntoa(tGetSockAddr.sin_addr));while(1){ scanf("%s", ucSendBuf); iError = send(iSocketFd, ucSendBuf, sizeof(ucSendBuf), 0); if(iError <= 0){ printf("Send buffer error\n"); } iRecvBufLen = recv(iSocketFd, ucRecvBuf, sizeof(ucRecvBuf), 0); if(iRecvBufLen > 0){ ucRecvBuf[iRecvBufLen] = '\0'; printf("%s\n", ucRecvBuf); }else{ printf("Receive error\n"); }}
服务器端
if(!fork()){ /* 创建子线程,!fork() 表明是主进程 */ while(1){ iRecvBufLen = recv(iSocketclientFd, ucRecvBuf, sizeof(ucRecvBuf), 0); if(iRecvBufLen > 0){ ucRecvBuf[iRecvBufLen] = '\0'; printf("Receive buf %s, form %s\n", ucRecvBuf, (char *)inet_ntoa(tClientAddr.sin_addr.s_addr)); send(iSocketclientFd, "I received your buff", 256, 0); }else{ close(iSocketclientFd); return -1; } }}
- 客户端,不同的主机,如果没有初始化绑定 sockfd
struct socketaddr length = 16struct socketaddr_in length = 16tGetSockAddr.sin_family = 0tGetSockAddr.sin_port = 0tGetSockAddr.sin_addr = 3.0.0.0sdfhdsjfI received your buff
- 客户端,相同主机,初始化绑定 sockfd
struct socketaddr length = 16struct socketaddr_in length = 16Bind error //绑定出错tGetSockAddr.sin_family = 2tGetSockAddr.sin_port = 12345tGetSockAddr.sin_addr = 192.168.3.56
不同的主机绑定成功,同一个主机绑定就不成功
4. 接收与发送数据
- send 要事先通过 connect 进行连接,面向连接
- recv 接受数据,数据中不含有发送者的地址信息
- sendto 不需要实现建立连接,参数包含目标地址,无连接
- recvfrom 接收信息中含有发送端的地址信息
0 0
- 网络编程---Socket编程
- 网络编程:Socket编程
- 网络编程(Socket编程)
- 网络socket编程指南
- 网络socket编程指南
- 网络Socket编程
- 网络socket编程指南
- 网络socket编程指南
- 网络socket编程指南
- 网络socket编程指南
- 网络编程 socket
- 网络编程 socket
- 网络socket编程指南
- 网络socket编程指南
- 网络socket编程指南
- 网络socket编程
- 网络socket编程指南
- 网络socket编程指南
- Why does this code using `::boost::bind` get a compiler error?
- kaldi 跑数据过程中遇到的错误
- emscripten
- perl 的初次接触
- MongoDB小结30 - 聚合管道【$limit】
- socket网络编程
- find the safest road
- javascript进阶之基础篇二:对象
- LeetCode----344. Reverse String 字符串反转
- Layout布局中Button被拉伸解决方法
- 参照数据字典中的表类型生成内表对象或结构体
- KVM源代码分析2:虚拟机的创建与运行
- 160803
- android中广播的理解