《Linux C/C++服务器开发实践》之第4章 TCP服务器编程
《Linux C/C++服务器开发实践》之第4章 TCP服务器编程
- 4.1 套接字的基本概念
- 4.2 网络程序的架构
- 4.3 IP地址的格式转换
- 4.1.c
- 4.4 套接字的类型
- 4.5 套接字地址
- 4.5.1 通用socket地址
- 4.5.2 专用socket地址
- 4.5.3 获取套接字地址
- 4.2.c
- 4.6 主机字节序和网络字节序
- 4.3.c
- 4.7 协议族和地址族
- 4.8 TCP套接字编程的基本步骤
- 4.9 TCP套接字编程的相关函数
- 4.9.1 BSD socket的头文件
- 4.9.2 socket函数
- 4.9.3 bind函数
- 4.9.4 listen函数
- 4.9.5 accept函数
- 4.9.6 connect函数
- 4.9.7 send函数
- 4.9.8 recv函数
- 4.9.9 close函数
- 4.10 简单的TCP套接字编程
- 4.4.server.c
- 4.4.client.c
- 4.5.c
- 4.11 深入理解TCP编程
- 4.11.1 数据发送和接收涉及的缓冲区
- 4.11.2 TCP数据传输的特点
- 4.11.3 数据发送的六种情形
- 4.11.4 数据接收时的情形
- 4.11.5 一次请求响应的数据接收
- 4.6.server.c
- 4.6.client.c
- 4.11.6 多次请求响应的数据接收
- 4.7.server.c
- 4.7.client.c
- 4.8.c
- 4.9.server.c
- 4.9.client.c
- 4.12 I/O控制命令
- 4.10.c
- 4.13 套接字选项
- 4.13.1 基本概念
- 4.13.2 选项的级别
- 4.13.3 获取套接字选项
- 4.11.c
- 4.12.c
- 4.13.c
- 4.13.4 设置套接字选项
- 4.14.c
4.1 套接字的基本概念
套接字是TCP/IP网络编程中的基本操作单元,不同主机的进程之间的相互通信的端点。
socket是在应用层和传输层之间的一个抽象层,它把TCP/IC层复杂的操作抽象为几个简单的接口,供应用层调用已实现进程在网络中通信。
4.2 网络程序的架构
B/S(Browser/Server,浏览器/服务器)架构,用户只需要浏览器就行,主要逻辑在服务器完成,减轻了客户端的升级和维护的工作量。
C/S(Client/Server,客户机/服务器)架构,客户端和服务端安装不同应用软件,客户端软件安装或升级比较复杂,维护成本大,可以充分利用两端的硬件能力,较为合理的分配任务。
客户机和服务器间的通信过程:
(1)客户机向服务器提出一个请求。
(2)服务器收到客户机的请求,进行分析处理。
(3)服务器将处理的结果返回给客户机。
4.3 IP地址的格式转换
#include <arpa/inet.h>
//字符串IP地址转换为网络字节序存储在addr中,并返回该网络字节序表示的无符号整数。
//失败:返回0
int inet_aton(const char *IP, struct in_addr *addr);//返回网络字节序
//uint32,失败-1
in_addr_t inet_addr(const char* cp);//及时复制返回的字符串
char *inet_ntoa(struct in_addr in);
#include <arpa/inet.h>
#include <stdlib.h>
#include <iostream>int main()
{char IP[] = "159.12.8.109";in_addr address;int number = inet_aton(IP, &address);//将点分十进制的IP地址转化为二进制的网络字节序if(number == 0){std::cerr<<"error IP!";exit(1);}std::cout << number << std::endl;std::cout << inet_ntoa(address) << std::endl;//将网络字节序地址转化为点分十进制表示形式return 0;
}
4.1.c
#include <stdio.h>
#include <arpa/inet.h>int main()
{in_addr_t dwIP = inet_addr("172.16.2.6");struct in_addr ia;ia.s_addr = dwIP;printf("ia.s_addr = %#x\n", ia.s_addr);printf("real_ip = %s\n", inet_ntoa(ia));return 0;
}
4.4 套接字的类型
(1)流套接字(SOCK_STREAM)
用于提供面向连接的、可靠的数据传输服务,无数据边界(收发次数不一致),可保证数据能够无差别、无重复发送,并按顺序接收(因为使用了传输控制协议即TCP)。
(2)数据报套接字(SOCK_DGRAM)
提供无连接的服务,不保证数据传输的可靠性,有数据边界(数据报收发次数一致),数据传输过程中可能丢失或重复,且无法保证接收数据有序。(使用UDP传输)
(3)原始套接字(SOCK_RAW)
允许对较低层次的协议(IP、ICMP等)直接访问,常用于检验新的协议实现,或者访问现有服务中配置的新设备。
能够控制网络底层传输机制,所以可以应用原始套接字操纵网络层和传输层应用。接收ICMP、IGMP协议包,接收TCP/IP栈不能处理的IP包,发送自定义报头或自定义协议的IP包。
能够读写内核没有处理的IP数据报。
4.5 套接字地址
包含IP地址和端口信息,识别主机及进程。
4.5.1 通用socket地址
表示大多数网络地址,用于socket API函数中。
struct sockaddr{sa_family_t sa_family;char sa_data[14];
};
sa_family_t:地址族或协议族类型
- PF_UNIX: UNIX本地域协议族
- PF_INET: IPv4协议族
- PF_INET6: IPv6协议族
- AF_UNIX: UNIX本地域地址族
- AF_INET: IPv4地址族
- AF_INET: IPv6地址族
sa_data:存放具体的地址数据(IP和端口)
协议族 | 地址的含义和长度 |
---|---|
PF_INET | 32位IPV4地址和16位端口号,共6字节 |
PF_INET6 | 128位IPv6地址、16位端口号、32位流标识和32位范围ID,共26字节 |
PF_UNIX | 文件全路劲名,最大长度可达108字节 |
4.5.2 专用socket地址
不同协议族定义的不同socket地址结构体,各个信息用不同字段表示。
一般强制转换为通用地址结构使用。
/* Structure describing an Internet socket address. */
struct sockaddr_in{__SOCKADDR_COMMON (sin_);in_port_t sin_port; /* Port number. */struct in_addr sin_addr; /* Internet address. *//* Pad to size of `struct sockaddr'. */unsigned char sin_zero[sizeof (struct sockaddr)- __SOCKADDR_COMMON_SIZE- sizeof (in_port_t)- sizeof (struct in_addr)];};/* Internet address. */
typedef uint32_t in_addr_t;
struct in_addr{in_addr_t s_addr;};
#if !__USE_KERNEL_IPV6_DEFS
/* Ditto, for IPv6. */
struct sockaddr_in6{__SOCKADDR_COMMON (sin6_);in_port_t sin6_port; /* Transport layer port # */uint32_t sin6_flowinfo; /* IPv6 flow information */struct in6_addr sin6_addr; /* IPv6 address */uint32_t sin6_scope_id; /* IPv6 scope-id */};
#endif /* !__USE_KERNEL_IPV6_DEFS */#if !__USE_KERNEL_IPV6_DEFS
/* IPv6 address */
struct in6_addr{union{uint8_t __u6_addr8[16];uint16_t __u6_addr16[8];uint32_t __u6_addr32[4];} __in6_u;
#define s6_addr __in6_u.__u6_addr8
#ifdef __USE_MISC
# define s6_addr16 __in6_u.__u6_addr16
# define s6_addr32 __in6_u.__u6_addr32
#endif};
#endif /* !__USE_KERNEL_IPV6_DEFS */
4.5.3 获取套接字地址
#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
//成功0,失败-1
//socklen_t addrlen = sizeof(struct sockaddr_in);
getsockname获取本地套接字地址的情况:
- 本地套接字已bind地址
- 本地套接字已connet到远程,内核会分配地址
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
//成功0,失败-1
//socklen_t addrlen = sizeof(struct sockaddr_in);
4.2.c
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>int main()
{int sfp = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sfp){printf("socket() fail!\n");return -1;}printf("socket() ok!\n");char on = 1;setsockopt(sfp, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));struct sockaddr_in serv = {0};int serv_len = sizeof(serv);printf("ip=%s, port=%d\n", inet_ntoa(serv.sin_addr), ntohs(serv.sin_port));getsockname(sfp, (struct sockaddr *)&serv, (socklen_t *)&serv_len);printf("ip=%s, port=%d\n", inet_ntoa(serv.sin_addr), ntohs(serv.sin_port));unsigned short portnum = 10051;struct sockaddr_in s_add;memset(&s_add, 0, sizeof(struct sockaddr_in));s_add.sin_family = AF_INET;s_add.sin_addr.s_addr = inet_addr("127.0.0.1");s_add.sin_port = htons(portnum);if (-1 == bind(sfp, (struct sockaddr *)(&s_add), sizeof(struct sockaddr))){printf("bind() fail: %d!\n", errno);return -1;}printf("bind() ok!\n");getsockname(sfp, (struct sockaddr *)&serv, (socklen_t *)&serv_len);printf("ip=%s, port=%d\n", inet_ntoa(serv.sin_addr), ntohs(serv.sin_port));return 0;
}
4.6 主机字节序和网络字节序
- 小端字节序Little Endian
底地址存放低字节
大端字节序Big Endian(网络字节序)
底地址存放高字节
uint32_t主机字节序转网络字节序
htonl
uint16_t主机字节序转网络字节序
htonsuint32_t网络字节序转主机字节序
ntohl
uint16_t网络字节序转主机字节序
ntohs
4.3.c
#include <iostream>
using namespace std;int main()
{int nNum = 0x12345678;char *p = (char *)&nNum;if (*p == 0x12)cout << "This machine is big endian." << endl;elsecout << "This machine is small endian." << endl;return 0;
}
4.7 协议族和地址族
-
协议族
不同协议的集合,宏以PF_开头,PROTOCOL FAMILY -
地址族
协议族所使用的地址集合(不同网络协议使用不同网络地址),宏以AF_开头,Address Family
地址族和协议族的值一样,都用来标识不同的一套协议。
4.8 TCP套接字编程的基本步骤
- 服务器编程步骤:
一、创建套接字,socket函数
二、绑定套接字到IP地址和端口,bind函数
三、套接字设置为监听模式并等待连接请求,listen函数
四、请求到来时,接受连接请求,返回对应连接的套接字,accept函数
五、用该连接套接字同客户端通信,send或recv函数,通信结束关闭,closesocket函数
六、监听套接字等待其他客户端的连接
七、推出服务器程序,关闭监听套接字,closesocket函数
客户端编程步骤:
一、创建套接字,socket函数
二、向服务器发出请求连接,connect函数
三、同服务器端通信,send或recv函数,
四、通信结束关闭,closesocket函数
4.9 TCP套接字编程的相关函数
4.9.1 BSD socket的头文件
- <sys/socket.h>:核心函数和数据结构的声明
- <netinet/in.h>:地址族和协议族,IP地址和端口号等
- <bits/socket.h>:地址族和协议族的宏定义
- <arpa/inet.h>:IP地址相关函数
- <netdb.h>:协议名和主机名转化为数字的函数
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
4.9.2 socket函数
创建套接字,并分配系统资源
int socket(int domain, int type, int protocol);
//AF_INET
//SOCK_STREAM, SOCK_DGRAM, SOCK_RAW
//IPPROTO_TCP, IPPROTO_UDP, 0
//默认都是阻塞
4.9.3 bind函数
本地地址信息关联到套接字上。
int bind(int sockfd, const struct* addr, socklen_t addrlen);
//成功0,失败-1,errno获取错误码
sockaddr_in in;
in_addr_t ip = inet_addr("192.168.13.25");
if(ip != -1)in.sin_addr.s_addr = ip;//#define INADDR_ANY ((in_addr_t) 0x00000000)
in.sin_addr.s_addr = htonl(INADDR_ANY);//errno,98,端口占用、未释放或程序未正常结束
4.9.4 listen函数
套接字处于监听状态。
int listen(int sockfd, int backlog);
//成功0,失败-1
4.9.5 accept函数
从监听套接字的客户连接请求队列获取客户端请求,并创建新的套接字来和客户端通信。
int accept(int sockfd, struct sockaddr *addr, socklen_t * addrlen);
//失败-1
4.9.6 connect函数
请求与监听套接字建立连接。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//成功0,失败-1
非阻塞时,可设置连接超时时间,通过error中EINRPOCESS(Operation now in progress)判断。
4.9.7 send函数
发送数据,复制到套接字的发送缓冲区。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//成功返回发送拷贝字节数,对方关闭返回0,错误-1
TCP有发送缓冲区,UDP无发送缓冲区。
非阻塞,可利用error变量EAGAIN
send用于有连接的套接字。
sendto和sendmsg用于有或无连接的套接字。
4.9.8 recv函数
接收数据.
ssize_t recv(int sockfd, const void *buf, size_t len, int flags);
//成功返回接收字节数,对方关闭返回0,错误-1,errno是EINTR、EWOULDBLOCK或EAGAIN时连接正常
recvfrom也能接收数据。
4.9.9 close函数
关闭套接字
#include <unistd.h>
int close(int fd);
//成功0,失败-1
4.10 简单的TCP套接字编程
4.4.server.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>int main()
{int sockSrv = socket(AF_INET, SOCK_STREAM, 0);char on = 1;setsockopt(sockSrv, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));struct sockaddr_in addrSrv;memset(&addrSrv, 0, sizeof(struct sockaddr_in));addrSrv.sin_addr.s_addr = inet_addr("127.0.0.1");addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(8000);if (-1 == bind(sockSrv, (struct sockaddr *)&addrSrv, sizeof(struct sockaddr))){printf("bind() fail: %d!\n", errno);return -1;}const int len = sizeof(struct sockaddr_in);struct sockaddr_in serv;getsockname(sockSrv, (struct sockaddr *)&serv, (socklen_t *)&len);printf("server has started, ip=%s, port=%d\n", inet_ntoa(serv.sin_addr), ntohs(serv.sin_port));listen(sockSrv, 5);struct sockaddr_in addrClient;while (1){printf("--------wait for client-----------\n");int sockConn = accept(sockSrv, (struct sockaddr *)&addrClient, (socklen_t *)&len);char sendBuf[100];sprintf(sendBuf, "Welcome client(%s: %d) to Server!", inet_ntoa(addrClient.sin_addr), ntohs(addrClient.sin_port));send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);char recvBuf[100];recv(sockConn, recvBuf, 100, 0);printf("Receive client's msg: %s\n", recvBuf);close(sockConn);/*puts("continue to listen?(y/n)");char ch[2];scanf("%s", ch, 2);if (ch[0] != 'y')break;*/}close(sockSrv);return 0;
}
4.4.client.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>int main()
{int sockClient = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in addrSrv;addrSrv.sin_addr.s_addr = inet_addr("127.0.0.1");addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(8000);int err = connect(sockClient, (struct sockaddr *)&addrSrv, sizeof(struct sockaddr));if (-1 == err){printf("Failed to connect to the server.Please check whether the server is started\n");return 0;}char recvBuf[100] = {0};recv(sockClient, recvBuf, 100, 0);printf("receive server's msg: %s\n", recvBuf);char msg[] = "hi,server";send(sockClient, msg, strlen(msg) + 1, 0);close(sockClient);return 0;
}
4.5.c
#include <stdio.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>#define BUFFER_SIZE 512unsigned long GetTickCount()
{struct timeval tv;if (gettimeofday(&tv, NULL) != 0)return 0;return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
}int main()
{struct sockaddr_in server_address;memset(&server_address, 0, sizeof(server_address));server_address.sin_family = AF_INET;server_address.sin_addr.s_addr = inet_addr("192.168.0.88");server_address.sin_port = htons(13334);int sock = socket(PF_INET, SOCK_STREAM, 0);assert(sock >= 0);long t1 = GetTickCount();int ret = connect(sock, (struct sockaddr *)&server_address, sizeof(server_address));if (ret == -1){long t2 = GetTickCount();printf("connect() failed: %d\n", ret);printf("time used: %ldms\n", t2 - t1);if (errno == EINPROGRESS)printf("unblock mode ret code...\n");}elseprintf("ret code is: %d\n", ret);close(sock);return 0;
}
4.11 深入理解TCP编程
4.11.1 数据发送和接收涉及的缓冲区
应用缓冲区和TCP套接字缓冲区(内核缓冲区)。
4.11.2 TCP数据传输的特点
一、字节流,无消息边界。
二、send后并不立即发送数据,内核控制。
三、数据发送速度,网络状态决定。
四、控制数据真实发送,网络状态决定。
五、recv时并不知道真实已接收多少数据。
4.11.3 数据发送的六种情形
假设调用两次send,发送数据A和数据B。(send(A),send(B)),真实发送情况:
一、网络情况良好,未受发送窗口、拥塞窗口和TCP最大传输单元影响,A、B变成两个数据段发送。
二、网络不好,发送A被延迟,A、B数据合并,且长度未超过窗口大小和最大传输单元。AB合并发送一次。
三、网络不好,发送A被延迟,A、B数据合并,且长度超过窗口大小或最大传输单元。AB合并发送(AB1、B2)。
四、网络不好,发送A被延迟,A、B数据合并,且长度超过窗口大小或最大传输单元。AB合并发送(A1、A2B)。
五、接收窗口小,AB分成多份发送。
六、发送错误,失败。
4.11.4 数据接收时的情形
- 接收到本次达到接收端的全部数据
- 接收到达到接收端的部分数据
- 没有收到数据
send与实际发送次数无关,send与recv次数无关。
4.11.5 一次请求响应的数据接收
接收到全部数据后断开连接,通过recv返回0判断发送方数据发送完毕。
4.6.server.c
#include <stdio.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>#define BUF_LEN 300typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;int main()
{int sockSrv = socket(AF_INET, SOCK_STREAM, 0);assert(sockSrv >= 0);char on = 1;setsockopt(sockSrv, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));SOCKADDR_IN addrSrv;addrSrv.sin_addr.s_addr = inet_addr("127.0.0.1");addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(8000);bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));listen(sockSrv, 5);const int len = sizeof(SOCKADDR);SOCKADDR_IN addrClient;while (1){printf("--------wait for client-----------\n");int sockConn = accept(sockSrv, (SOCKADDR *)&addrClient, (socklen_t *)&len);char sendBuf[100] = "";for (int i = 0; i < 10; i++){memset(sendBuf, 0, sizeof(sendBuf));sprintf(sendBuf, "N0.%d Welcome to the server. What is 1 + 1 = ? (client IP: %s, client Port: %d)\n", i + 1, inet_ntoa(addrClient.sin_addr), ntohs(addrClient.sin_port));send(sockConn, sendBuf, strlen(sendBuf), 0);}int iRes = shutdown(sockConn, SHUT_WR);if (iRes == -1){printf("shutdown failed with error: %d\n", errno);close(sockConn);return 1;}char recvBuf[BUF_LEN];do{iRes = recv(sockConn, recvBuf, BUF_LEN, 0);if (iRes > 0){printf("Recv %d bytes.\n", iRes);for (int i = 0; i < iRes; i++)printf("%c", recvBuf[i]);printf("\n");}else if (iRes == 0){printf("The client has closed the connection.\n");}else{printf("recv failed with error: %d\n", errno);close(sockConn);return 1;}} while (iRes > 0);close(sockConn);/*puts("Continue monitoring?(y/n)");char ch[2];scanf("%s", ch, 2);if (ch[0] != 'y')break;*/}close(sockSrv);return 0;
}
4.6.client.c
#include <stdio.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>#define BUF_LEN 300typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;int main()
{int sockClient = socket(AF_INET, SOCK_STREAM, 0);SOCKADDR_IN addrSrv;addrSrv.sin_addr.s_addr = inet_addr("127.0.0.1");addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(8000);int err = connect(sockClient, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));if (-1 == err){printf("Failed to connect to the server. Please check whether the server is started\n");return 0;}char recvBuf[BUF_LEN];int iRes;do{iRes = recv(sockClient, recvBuf, BUF_LEN, 0);if (iRes > 0){printf("\nRecv %d bytes:", iRes);for (int i = 0; i < iRes; i++)printf("%c", recvBuf[i]);printf("\n");}else if (iRes == 0){puts("The server has closed the send connection.\n");}else{printf("recv failed:%d\n", errno);close(sockClient);return 1;}} while (iRes > 0);char sendBuf[100];for (int i = 0; i < 10; i++){memset(sendBuf, 0, sizeof(sendBuf));sprintf(sendBuf, "N0.%d I'm the client, 1+1=2\n", i + 1);send(sockClient, sendBuf, strlen(sendBuf) + 1, 0);}puts("Sending data to the server is completed.");close(sockClient);return 0;
}
4.11.6 多次请求响应的数据接收
- 定长数据的接收
4.7.server.c
#include <stdio.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>#define BUF_LEN 300typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;int main()
{int sockSrv = socket(AF_INET, SOCK_STREAM, 0);assert(sockSrv >= 0);char on = 1;setsockopt(sockSrv, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));SOCKADDR_IN addrSrv;addrSrv.sin_addr.s_addr = inet_addr("127.0.0.1");addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(8000);bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));listen(sockSrv, 5);const int len = sizeof(SOCKADDR);SOCKADDR_IN addrClient;char sendBuf[111];char recvBuf[BUF_LEN];while (1){printf("--------wait for client-----------\n");int sockConn = accept(sockSrv, (SOCKADDR *)&addrClient, (socklen_t *)&len);printf("--------client comes-----------\n");memset(sendBuf, 'a', 111);for (int cn = 0; cn < 50; cn++){if (cn == 49)sendBuf[110] = 'b';send(sockConn, sendBuf, 111, 0);}int iRes;do{iRes = recv(sockConn, recvBuf, BUF_LEN, 0);if (iRes > 0){printf("\nRecv %d bytes:", iRes);for (int i = 0; i < iRes; i++)printf("%c", recvBuf[i]);printf("\n");}else if (iRes == 0){printf("The client closes the connection.\n");}else{printf("recv failed with error: %d\n", errno);close(sockConn);return 1;}} while (iRes > 0);close(sockConn);/*puts("Continue monitoring?(y/n)");char ch[2];scanf("%s", ch, 2);if (ch[0] != 'y')break;*/}close(sockSrv);return 0;
}
4.7.client.c
#include <stdio.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>#define BUF_LEN 250typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;int main()
{int sockClient = socket(AF_INET, SOCK_STREAM, 0);SOCKADDR_IN addrSrv;addrSrv.sin_addr.s_addr = inet_addr("127.0.0.1");addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(8000);int err = connect(sockClient, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));if (-1 == err){printf("Failed to connect to the server:%d\n", errno);return 0;}char recvBuf[BUF_LEN];int iRes;int cn = 1;for (int leftlen = 50 * 111; leftlen > 0; leftlen -= iRes){iRes = recv(sockClient, recvBuf, BUF_LEN, 0);if (iRes > 0){printf("\nNo.%d: Recv %d bytes: ", cn++, iRes);for (int i = 0; i < iRes; i++)printf("%c", recvBuf[i]);printf("\n");}else if (iRes == 0){puts("\nThe server has closed the send connection.\n");}else{printf("recv failed: %d\n", errno);close(sockClient);return -1;}}/*int leftlen = 50 * 111;while (leftlen > BUF_LEN){iRes = recv(sockClient, recvBuf, BUF_LEN, 0);if (iRes > 0){printf("\nNo.%d: Recv %d bytes: ", cn++, iRes);for (int i = 0; i < iRes; i++)printf("%c", recvBuf[i]);printf("\n");}else if (iRes == 0){puts("\nThe server has closed the send connection.\n");}else{printf("recv failed: %d\n", errno);close(sockClient);return -1;}leftlen -= iRes;}if (leftlen > 0){iRes = recv(sockClient, recvBuf, leftlen, 0);if (iRes > 0){printf("\nNo.%d: Recv %d bytes: ", cn++, iRes);for (int i = 0; i < iRes; i++)printf("%c", recvBuf[i]);printf("\n");}else if (iRes == 0){puts("\nThe server has closed the send connection.\n");}else{printf("recv failed: %d\n", errno);close(sockClient);return -1;}leftlen -= iRes;}
*/char sendBuf[100];memset(sendBuf, 0, sizeof(sendBuf));sprintf(sendBuf, "Hi, Server, I've finished receiving the data.");send(sockClient, sendBuf, strlen(sendBuf) + 1, 0);puts("Sending data to the server is completed");close(sockClient);return 0;
}
- 变长数据的接收
数据报末尾加结束标识符
变长消息体前加固定长度的报头,报头内加入消息体长度字段
struct MyData
{int nLen;char data[0];
};
struct MyData* p = (struct MyData*)malloc(sizeof(struct MyData)+strlen(str));
4.8.c
#include <cstdio>
#include <iostream>
#include <string.h>
using namespace std;struct MyData
{int nLen;char data[0];
};int main()
{cout << "Size of MyData: " << sizeof(MyData) << endl;char str[10] = "123456";int nLen = sizeof(str);MyData *myData = (MyData *)malloc(sizeof(MyData) + nLen);myData->nLen = nLen;memcpy(myData->data, str, nLen);cout << "myData's Data is: " << myData->data << endl;cout << "Size of MyData: " << sizeof(MyData) << endl;free(myData);return 0;
}
4.9.server.c
#include <cstdio>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <malloc.h>#define BUF_LEN 300typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;struct MyData
{int nLen;char data[0];
};int main()
{int sockSrv = socket(AF_INET, SOCK_STREAM, 0);SOCKADDR_IN addrSrv;addrSrv.sin_addr.s_addr = inet_addr("127.0.0.1");addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(8000);bind(sockSrv, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));listen(sockSrv, 5);SOCKADDR_IN addrClient;int len = sizeof(SOCKADDR);int cn = 5550;struct MyData *mydata;int iRes;char recvBuf[BUF_LEN];while (1){printf("--------wait for client-----------\n");int sockConn = accept(sockSrv, (SOCKADDR *)&addrClient, (socklen_t *)&len);printf("--------client comes-----------\n");mydata = (MyData *)malloc(sizeof(MyData) + cn);mydata->nLen = htonl(cn);memset(mydata->data, 'a', cn);mydata->data[cn - 1] = 'b';send(sockConn, (char *)mydata, sizeof(MyData) + cn, 0);free(mydata);do{iRes = recv(sockConn, recvBuf, BUF_LEN, 0);if (iRes > 0){printf("\nRecv %d bytes: ", iRes);for (int i = 0; i < iRes; i++)printf("%c", recvBuf[i]);printf("\n");}else if (iRes == 0){printf("\nThe client has closed the connection.\n");}else{printf("recv failed with error: %d\n", errno);close(sockConn);return 1;}} while (iRes > 0);close(sockConn);/*puts("Continue monitoring?(y/n)");char ch[2];scanf("%s", ch, 2);if (ch[0] != 'y')break;*/}close(sockSrv);return 0;
}
4.9.client.c
#include <stdio.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <malloc.h>#define BUF_LEN 250typedef struct sockaddr_in SOCKADDR_IN;
typedef struct sockaddr SOCKADDR;int main()
{int sockClient = socket(AF_INET, SOCK_STREAM, 0);SOCKADDR_IN addrSrv;addrSrv.sin_addr.s_addr = inet_addr("127.0.0.1");addrSrv.sin_family = AF_INET;addrSrv.sin_port = htons(8000);int err = connect(sockClient, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));if (-1 == err){printf("Failed to connect to the server:%d\n", errno);return 0;}int leftlen;int iRes = recv(sockClient, (char *)&leftlen, sizeof(int), 0);leftlen = ntohl(leftlen);printf("Need to receive %d bytes data.\n", leftlen);char recvBuf[BUF_LEN];int cn = 1;for (; leftlen > 0; leftlen -= iRes){if (leftlen < BUF_LEN)iRes = recv(sockClient, recvBuf, leftlen, 0);elseiRes = recv(sockClient, recvBuf, BUF_LEN, 0);if (iRes > 0){printf("\nNo.%d: Recv %d bytes: ", cn++, iRes);for (int i = 0; i < iRes; i++)printf("%c", recvBuf[i]);printf("\n");}else if (iRes == 0){puts("\nThe server has closed the send connection.\n");}else{printf("recv failed: %d\n", errno);close(sockClient);return -1;}}/*while (leftlen > BUF_LEN){iRes = recv(sockClient, recvBuf, BUF_LEN, 0);if (iRes > 0){printf("\nNo.%d: Recv %d bytes: ", cn++, iRes);for (int i = 0; i < iRes; i++)printf("%c", recvBuf[i]);printf("\n");}else if (iRes == 0){puts("\nThe server has closed the send connection.\n");}else{printf("recv failed: %d\n", errno);close(sockClient);return -1;}leftlen -= iRes;}if (leftlen > 0){iRes = recv(sockClient, recvBuf, leftlen, 0);if (iRes > 0){printf("\nNo.%d: Recv %d bytes: ", cn++, iRes);for (int i = 0; i < iRes; i++)printf("%c", recvBuf[i]);printf("\n");}else if (iRes == 0){puts("\nThe server has closed the send connection.\n");}else{printf("recv failed: %d\n", errno);close(sockClient);return -1;}leftlen -= iRes;}*/char sendBuf[100];memset(sendBuf, 0, sizeof(sendBuf));sprintf(sendBuf, "I'm the client. I've finished receiving the data.");send(sockClient, sendBuf, strlen(sendBuf) + 1, 0);puts("Sending data to the server is completed");close(sockClient);return 0;
}
4.12 I/O控制命令
设置套接字的工作模式(阻塞或非阻塞),获取套接字I/O操作的参数信息。
#include <sys/ioctl.h>
int ioctl(int fd, int request, ...);
//成功0,失败-1,errno错误码
I/O控制命令request:
- FIONBIO
阻塞 - FIONREAD
流套接字,recv一次可读入数据量;数据报套接字,第一个数据报大小;缓冲区大小。 - FIOASYNC
异步
int iMode = 0;
ioctl(m_socket, FIONBIO, &iMode); //阻塞模式int num = 0;
ioctl(0, FIONREAD, &iMode); //标准输入缓冲区字节数
4.10.c
#include <stdio.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>unsigned long GetTickCount()
{struct timeval tv;if (gettimeofday(&tv, NULL) != 0)return 0;return (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
}int main()
{char ip[] = "192.168.0.88";in_addr_t dwIP = inet_addr(ip);int port = 13334;struct sockaddr_in server_address;memset(&server_address, 0, sizeof(server_address));server_address.sin_family = AF_INET;server_address.sin_addr.s_addr = dwIP;server_address.sin_port = htons(port);int sock = socket(PF_INET, SOCK_STREAM, 0);assert(sock >= 0);long t1 = GetTickCount();int ret = connect(sock, (struct sockaddr *)&server_address, sizeof(server_address));printf("connect ret code is: %d\n", ret);if (ret == -1){long t2 = GetTickCount();printf("time used: %ldms\n", t2 - t1);printf("connect() failed...\n");if (errno == EINPROGRESS)printf("unblock mode ret code...\n");}elseprintf("ret code is: %d\n", ret);int argp = 1;int res = ioctl(sock, FIONBIO, &argp);if (-1 == res){printf("Error at ioctlsocket(): %d\n", errno);return -1;}puts("\nAfter setting non blocking mode:");/*memset(&server_address, 0, sizeof(server_address));server_address.sin_family = AF_INET;server_address.sin_addr.s_addr = dwIP;server_address.sin_port = htons(port);*/t1 = GetTickCount();ret = connect(sock, (struct sockaddr *)&server_address, sizeof(server_address));printf("connect ret code is: %d\n", ret);if (ret == -1){long t2 = GetTickCount();printf("time used: %ldms\n", t2 - t1);if (errno == EINPROGRESS)printf("unblock mode errno: %d\n", errno);}elseprintf("ret code is: %d\n", ret);close(sock);return 0;
}
4.13 套接字选项
4.13.1 基本概念
设置或获取套接字属性
4.13.2 选项的级别
适用范围或适用对象,有些选项针对特定协议,有些选项适用所有类型套接字。
SO_TYPE
SO_SNDBUF
SO_REUSEADDR
SO_RCVBUF
SO_ERROR
…
4.13.3 获取套接字选项
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
//成功0,失败-1,errno错误码
//EBADF:sockfd不是有效文件描述符
//EFAULT:optlen太小或optval缓冲区非法
//EINVAL:level未知或非法
//ENOPROTOOPT:选项未知或不被指定协议族支持
//ENOTSOCK:sockfd不是套接字描述符
4.11.c
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>int main()
{int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (s == -1){printf("Error at socket()\n");return -1;}int su = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (s == -1){printf("Error at socket()\n");return -1;}int optVal;int optLen = sizeof(optVal);if (getsockopt(s, SOL_SOCKET, SO_RCVBUF, (char *)&optVal, (socklen_t *)&optLen) == -1)printf("getsockopt failed: %d", errno);elseprintf("Size of stream socket receive buffer: %d bytes\n", optVal);if (getsockopt(s, SOL_SOCKET, SO_SNDBUF, (char *)&optVal, (socklen_t *)&optLen) == -1)printf("getsockopt failed: %d", errno);elseprintf("Size of streaming socket send buffer: %d bytes\n", optVal);if (getsockopt(su, SOL_SOCKET, SO_RCVBUF, (char *)&optVal, (socklen_t *)&optLen) == -1)printf("getsockopt failed: %d", errno);elseprintf("Size of datagram socket receive buffer: %d bytes\n", optVal);if (getsockopt(su, SOL_SOCKET, SO_SNDBUF, (char *)&optVal, (socklen_t *)&optLen) == -1)printf("getsockopt failed: %d", errno);elseprintf("Size of datagram socket send buffer: %d bytes\n", optVal);return 0;
}
4.12.c
#include <stdio.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>int main()
{int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (s == -1){printf("Error at socket()\n");return -1;}int optVal;int optLen = sizeof(optVal);if (getsockopt(s, SOL_SOCKET, SO_TYPE, (char *)&optVal, (socklen_t *)&optLen) == -1)printf("getsockopt failed: %d", errno);else{if (SOCK_STREAM == optVal)printf("The current socket is a stream socket.\n");else if (SOCK_DGRAM == optVal)printf("The current socket is a datagram socket.\n");}int su = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (su == -1){printf("Error at socket()\n");return -1;}if (getsockopt(su, SOL_SOCKET, SO_TYPE, (char *)&optVal, (socklen_t *)&optLen) == -1)printf("getsockopt failed: %d", errno);else{if (SOCK_STREAM == optVal)printf("The current socket is a stream socket.\n");else if (SOCK_DGRAM == optVal)printf("The current socket is a datagram socket.\n");}return 0;
}
4.13.c
#include <stdio.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>typedef struct sockaddr SOCKADDR;int main()
{int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (s == -1){printf("Error at socket()\n");return -1;}char on = 1;setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));char ip[] = "127.0.0.1";struct sockaddr_in service;service.sin_family = AF_INET;service.sin_addr.s_addr = inet_addr(ip);service.sin_port = htons(8000);if (bind(s, (SOCKADDR *)&service, sizeof(service)) == -1){printf("bind failed: %d\n", errno);return -1;}int optVal;int optLen = sizeof(optVal);if (getsockopt(s, SOL_SOCKET, SO_ACCEPTCONN, (char *)&optVal, (socklen_t *)&optLen) == -1)printf("getsockopt failed: %d", errno);elseprintf("Before listening, The value of SO_ACCEPTCONN: %d, The socket is not listening\n", optVal);if (listen(s, 100) == -1){printf("listen failed: %d\n", errno);return -1;}if (getsockopt(s, SOL_SOCKET, SO_ACCEPTCONN, (char *)&optVal, (socklen_t *)&optLen) == -1){printf("getsockopt failed: %d", errno);return -1;}elseprintf("After listening, The value of SO_ACCEPTCONN: %d, The socket is listening\n", optVal);return 0;
}
4.13.4 设置套接字选项
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t *optlen);
//成功0,失败-1,errno错误码
4.14.c
#include <stdio.h>
#include <assert.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>typedef struct sockaddr SOCKADDR;int main()
{int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (s == -1){printf("Error at socket()\n");return -1;}char on = 1;setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));char ip[] = "127.0.0.1";struct sockaddr_in service;service.sin_family = AF_INET;service.sin_addr.s_addr = inet_addr(ip);service.sin_port = htons(9900);if (bind(s, (SOCKADDR *)&service, sizeof(service)) == -1){printf("bind failed\n");return -1;}int optVal = 1;int optLen = sizeof(int);if (getsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&optVal, (socklen_t *)&optLen) == -1){printf("getsockopt failed: %d", errno);return -1;}elseprintf("After bind, the value of SO_KEEPALIVE: %d\n", optVal);optVal = 1;if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&optVal, optLen) != -1)printf("Successful activation of keep alive mechanism.\n");if (getsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (char *)&optVal, (socklen_t *)&optLen) == -1){printf("getsockopt failed: %d", errno);return -1;}elseprintf("After setting, the value of SO_KEEPALIVE: %d\n", optVal);return 0;
}