《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主机字节序转网络字节序
htons
uint32_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;
else
cout << "This machine is small endian." << endl;
return 0;
}
4.7 协议族和地址族
-
协议族
不同协议的集合,宏以PF_开头,PROTOCOL FAMILY -
地址族
协议族所使用的地址集合(不同网络协议使用不同网络地址),宏以AF_开头,Address Family
地址族和协议族的值一样,都用来标识不同的一套协议。
TCP_287">4.8 TCP套接字编程的基本步骤
- 服务器编程步骤:
一、创建套接字,socket函数
二、绑定套接字到IP地址和端口,bind函数
三、套接字设置为监听模式并等待连接请求,listen函数
四、请求到来时,接受连接请求,返回对应连接的套接字,accept函数
五、用该连接套接字同客户端通信,send或recv函数,通信结束关闭,closesocket函数
六、监听套接字等待其他客户端的连接
七、推出服务器程序,关闭监听套接字,closesocket函数
客户端编程步骤:
一、创建套接字,socket函数
二、向服务器发出请求连接,connect函数
三、同服务器端通信,send或recv函数,
四、通信结束关闭,closesocket函数
TCP_302">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
TCP_411">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 512
unsigned 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");
}
else
printf("ret code is: %d\n", ret);
close(sock);
return 0;
}
TCP_573">4.11 深入理解TCP编程
4.11.1 数据发送和接收涉及的缓冲区
应用缓冲区和TCP套接字缓冲区(内核缓冲区)。
TCP_576">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 300
typedef 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 300
typedef 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 300
typedef 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 250
typedef 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 300
typedef 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 250
typedef 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);
else
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;
}
}
/*
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");
}
else
printf("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);
}
else
printf("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);
else
printf("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);
else
printf("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);
else
printf("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);
else
printf("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);
else
printf("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;
}
else
printf("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;
}
else
printf("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;
}
else
printf("After setting, the value of SO_KEEPALIVE: %d\n", optVal);
return 0;
}