《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 
//字符串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 
#include 
#include 

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 
#include 

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 
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
//成功0,失败-1
//socklen_t addrlen = sizeof(struct sockaddr_in);

getsockname获取本地套接字地址的情况:

  • 本地套接字已bind地址
  • 本地套接字已connet到远程,内核会分配地址
#include 
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
//成功0,失败-1
//socklen_t addrlen = sizeof(struct sockaddr_in);
4.2.c
#include 
#include 
#include 
#include 
#include 

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 
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

地址族和协议族的值一样,都用来标识不同的一套协议。

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的头文件

  • :核心函数和数据结构的声明
  • :地址族和协议族,IP地址和端口号等
  • :地址族和协议族的宏定义
  • :IP地址相关函数
  • :协议名和主机名转化为数字的函数
#include 
#include 
#include 
#include 
#include 

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 
int close(int fd);
//成功0,失败-1

4.10 简单的TCP套接字编程

4.4.server.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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 
#include 
#include 
#include 
#include 
#include 
#include 

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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;
}

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 数据接收时的情形

  1. 接收到本次达到接收端的全部数据
  2. 接收到达到接收端的部分数据
  3. 没有收到数据

send与实际发送次数无关,send与recv次数无关。

4.11.5 一次请求响应的数据接收

接收到全部数据后断开连接,通过recv返回0判断发送方数据发送完毕。

4.6.server.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 多次请求响应的数据接收

  1. 定长数据的接收
4.7.server.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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;
}
  1. 变长数据的接收
    数据报末尾加结束标识符
    变长消息体前加固定长度的报头,报头内加入消息体长度字段
struct MyData
{
	int nLen;
	char data[0];
};
struct MyData* p = (struct MyData*)malloc(sizeof(struct MyData)+strlen(str));
4.8.c
#include 
#include 
#include 
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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 
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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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 
#include 
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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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 
#include 
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t *optlen);
//成功0,失败-1,errno错误码
4.14.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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;
}

你可能感兴趣的:(C/C++,学习,整理,linux,TCP)