Linux网络编程——C++实现进程间TCP/IP通信

一、函数说明

  • 地址接口

1、通用地址接口

struct sockaddr
{
    u_short sa_family;  //地址类型,IPV4,用宏AG_INET即可;2字节;
    char sa_data[14];  //14字节的地址数据;
};

共16字节 = 2字节地址类型 + 14字节地址数据
2、自定义地址接口

struct sockaddr_in
{
    short int sin_family;  //地址族,IPv4,用宏AF_INET;
    unsigned short int sin_port;  //端口号,需要htons函数进行字节序转换;
    struct in_addr sin_addr;  //IP地址,需要inet_addr函数进行转换(点分字符串→数值),该成员本身也是一个结构体;
    unsigned char sin_zero[8];  //8个字节填0;
};
  • 地址转换

1、需要将点分字符串ip转化为程序ip,使用inet_addr函数:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//inet_addr需要包含以上三个头文件;
 
struct sockaddr_in servaddr;  //先定义一个该种地址接口的结构体变量;
servaddr.sin_addr.s_addr = inet_addr(argv[1]);

2、字节序转换
地址接口配置中的端口需要字节序转换,网络规定使用大端字节序。

#include <arpa/inet.h>  //头文件;
 
//从主机(h)发送到网络(n):
// 把unsigned long类型从主机序转换到网络序
uint32_t htonl(uint32_t hostlong);
// 把unsigned short类型从主机序转换到网络序
uint16_t htons(uint16_t hostshort);  //最常用!
 
//从网络(n)接收到主机(h):
// 把unsigned long类型从网络序转换到主机序
unit32_t ntohl(uint32_t netlong);
// 把unsigned short类型从网络序转换到主机序
unit16_t ntohs(uint16_t netshort);
  • 地址接口配置

1、socket:创建套接字

#include<sys/types.h>
#include<sys/socket.h>

int socket(int family, int type, int protocol);
参数含义
family协议族,对于IPV4用宏AF_INET;
type套接字类型:①对于tcp协议用宏SOCK_STREAM;②对于udp协议,用宏SOCK_DGRAM;
protocol指定为0即可;
返回值成功:返回文件描述符;失败:-1;

2、bind:绑定

#include<sys/types.h>
#include<sys/socket.h>

int bind(int sockfd, const struct sockaddr* servaddr, socklen_t addrlen);
参数含义
sockfd前面socket创建成功返回的套接字,即文件描述符;
servaddr配置好的通用地址接口.配置好的地址接口,属于struct sockaddr_in 类型,需要转换成第一种通用地址接口;注意是socklen_t类型,可以提前定义一个socklen_t类型变量,然后让其等于sizeof(servaddr)
addrlen配合第二个参数共同确认地址接口变量的内容;
返回值成功:0;失败:-1;

3、地址接口初始化

bzero(&servaddr, sizeof(servaddr));  //将地址接口结构体清空;
servaddr.sin_family = AF_INET;  //用宏AF_INET代表IPV4;
servaddr.sin_port = htons(8080);  //设置端口号(字节序转换);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  //这个宏表示任意IP地址,因为服务器不限定来访的客户端IP;

4、listen:监听(服务端专用)

#include<sys/types.h>
#include<sys/socket.h>

int listen(int sockfd, int backlog)
参数含义
sockfdsocket创建套接字的返回值
backlog服务器的监听长度,设置1024即可
返回值成功:0;失败:-1

5、accept:服务端连接

#include<sys/types.h>
#include<sys/socket.h>

int accept(int sockfd, struct sockaddr* cliaddr, socklen_t * addrlen)
参数含义
sockfdsocket创建套接字的返回值
cliaddr是一个输出参数,从accept带回来的的地址接口(客户端),属于struct sockaddr_in 类型,需要转换成第一种通用地址接口
addrlen配合第二个参数共同确认地址接口变量的内容,取该变量的地址
返回值成功:0;失败:-1

6、connect:客户端连接

#include<sys/types.h>
#include<sys/socket.h>

int connect(int sockfd, struct sockaddr* servaddr, socklen_t  addrlen)
参数含义
sockfdsocket创建套接字的返回值
servaddr配置好的地址接口,属于struct sockaddr_in 类型,需要转换成第一种通用地址接口
addrlen配合第二个参数共同确认地址接口变量的内容,注意不是地址,直接是该变量
返回值成功:0;失败:-1
  • 收发数据
    1、read/write
    因为socket返回的是文件描述符,因此也可以像文件一样使用read/write函数进行数据传输。
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

int write(int connfd,  void* buff, size_t length);
int read(int connfd,  void* buff, size_t length);
参数含义
sockfdsocket创建套接字的返回值
buff文件缓冲区,一般使用数组
length想要读(收)、写(发)的字节数,对于单独数组:strlen(buff),对于结构体数组:sizeof(node)
返回值成功:>0,实际发或者收到的字节数;0:遇到文件尾巴;失败:-1

2、send/recv

#include<sys/types.h>
#include<sys/socket.h>

size_t send(int connfd,  void* buff, size_t length, int flags);
size_t recv(int connfd,  void* buff, size_t length, int flags);

flags功能选项:
① 0:等同于write、read;
② MSG_DONTROUTE:send用,表示不查询路由表,在内部局域网进行通信;
③ MSG_OOB:接收或发送带外数据(插队);
④ MSG_PEEK:read用,表示读取时不删除数据;
⑤ MSG_WAITALL:等待所有数据(阻塞等待);
返回值 :
成功:>0,实际发或者收到的字节数;
0:对方已经下线;
失败:-1;

二、示例代码

客户端client.cpp:

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
#define SIZE 1024

void connectToServer(int &serverfd){
    char sendBuf[SIZE], recvBuf[SIZE];
    ssize_t sendRet, recvRet;

    while(1){
        // 发送数据及接收响应
        cout << ">> ";
        cin >> sendBuf;
        while(sendBuf){
            sendRet = send(serverfd, sendBuf, sizeof(sendBuf), 0);
            if(strcmp(sendBuf, "$") == 0)
                break;
            if (sendRet == -1)
                cout << "Error sending data\n";
            else 
                cout << "Sent data: " << sendBuf << endl;

            cout << ">> ";
            cin >> sendBuf;
        }
        
        // 接收响应及数据
        recvRet = recv(serverfd, recvBuf, sizeof(recvBuf), 0);
        while(recvRet > 0){
            recvBuf[recvRet] = '\0';
            if(strcmp(recvBuf, "$") == 0)
                break;
            cout << "Received data: " << recvBuf << endl;
            recvRet = recv(serverfd, recvBuf, sizeof(recvBuf), 0);
        }

        // 是否关闭连接
        cout << "Do you want to close connection? (y/n)";
        char c;
        while ((c = getchar()) != '\n' && c != EOF);
        cin >> sendBuf;
        while(strcmp(sendBuf, "y") !=0 && strcmp(sendBuf, "n") != 0){
            cout << "Wrong input! Please input a letter 'y' or 'n' : ";
            cin >> sendBuf;
        }
        sendRet = send(serverfd, sendBuf, sizeof(sendBuf), 0);
        if (sendRet == -1)
            cout << "Error sending data\n";
        if(strcmp(sendBuf, "y") == 0)
            break;
    }

    // 关闭套接字
    close(serverfd);
}

int main() {
    // 创建套接字
    int serverfd = socket(AF_INET, SOCK_STREAM, 0);
    if (serverfd == -1) {
        cerr << "Error creating socket\n";
        return 1;
    }

    // 连接到服务器
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8080); // 与服务器相同的端口号
    inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr); // 这里使用本地回环地址,如果服务器在另一台机器上,请替换为服务器的IP地址

    if (connect(serverfd, reinterpret_cast<struct sockaddr*>(&serverAddr), sizeof(serverAddr)) != -1) {
        cout << serverfd << " : Connected successfully!" << endl;
        connectToServer(serverfd);
    } else {
        cerr << "Error connecting to server\n";
        close(serverfd);
        return 1;
    }

    return 0;
}

服务端server.cpp:

  • 一对一通信
#include <iostream>
using namespace std;

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#define SIZE 1024

void connectToClient(int &clientfd) {
	char recvBuf[SIZE]; // 接收缓冲区
	char sendBuf[SIZE]; // 发送缓冲区
	int recvRet, sendRet; // 接受反馈及发送反馈

	while (clientfd != -1) {
		// receive data
		recvRet = recv(clientfd, recvBuf, sizeof(recvBuf), 0);
		while(recvRet > 0) {
			recvBuf[recvRet] = '\0';
			if(strcmp(recvBuf, "$") == 0)
				break;
			cout << "recv data from client " << clientfd << ", data : " << recvBuf << endl;
			recvRet = recv(clientfd, recvBuf, sizeof(recvBuf), 0);
		}

		// send data 
		cout << clientfd << ">> ";
		cin >> sendBuf;
		while(sendBuf) {
			sendRet = send(clientfd, sendBuf, sizeof(sendBuf), 0);
			if (strcmp(sendBuf, "$") == 0)
				break;
			if (sendRet == -1)
				cout <<"send data error." << endl;
			else
				cout << "Sent data to client: " << sendBuf << endl;
			cout << clientfd << ">> ";
			cin >> sendBuf;
		}

		// 是否关闭连接
		recvRet = recv(clientfd, recvBuf, sizeof(recvBuf), 0);
		if (recvRet == -1)
			cout << "Error receiving data\n";
		else if(strcmp(recvBuf, "y") == 0) {
			cout << "Have closed connection " << clientfd << '.' << endl;
			close(clientfd);
			break;
		}
	}
}

int main() {
	// create socket
	int serverfd = socket(AF_INET, SOCK_STREAM, 0); // IPV4 + TCP
	if(-1 == serverfd) {
		printf("create socket error");
		return -1;
	}
	
	// bind port 
	struct sockaddr_in bindaddr;
	bindaddr.sin_family = AF_INET;
	bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	bindaddr.sin_port = htons(8080);
	if(-1 == bind(serverfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr))) {
		cout << "bind error" <<  endl;
		return -1;
	}
	
	// start listen
	if (listen(serverfd, 1024) == -1) {
		printf("listem error");
		return -1;
	}

	while (1) {
		struct sockaddr_in clientaddr;
		socklen_t clientaddrlen = sizeof(clientaddr);
		
		// accept connection
		int clientfd = accept(serverfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
		if(clientfd!= -1) {
			// 获取客户端的信息
			char cliIp[16];
			inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, cliIp, sizeof(cliIp));
			unsigned short cliPort = ntohs(clientaddr.sin_port);
			cout << "client ip is : " << cliIp << ", port is : " << cliPort << endl;
			connectToClient(clientfd);
		}
	}
	
	//close socket
	close(serverfd);
	return 0;
}

  • 一对多通信(多进程)
#include <iostream>
using namespace std;

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#define SIZE 1024

void connectToClient(int &clientfd) {
	char recvBuf[SIZE]; // 接收缓冲区
	char sendBuf[SIZE]; // 发送缓冲区
	int recvRet, sendRet; // 接受反馈及发送反馈

	while (clientfd != -1) {
		// receive data
		recvRet = recv(clientfd, recvBuf, sizeof(recvBuf), 0);
		while(recvRet > 0) {
			recvBuf[recvRet] = '\0';
			if(strcmp(recvBuf, "$") == 0)
				break;
			cout << "recv data from client " << clientfd << ", data : " << recvBuf << endl;
			recvRet = recv(clientfd, recvBuf, sizeof(recvBuf), 0);
		}

		// send data 
		cout << clientfd << ">> ";
		cin >> sendBuf;
		while(sendBuf) {
			sendRet = send(clientfd, sendBuf, sizeof(sendBuf), 0);
			if (strcmp(sendBuf, "$") == 0)
				break;
			if (sendRet == -1)
				cout <<"send data error." << endl;
			else
				cout << "Sent data to client: " << sendBuf << endl;
			cout << clientfd << ">> ";
			cin >> sendBuf;
		}

		// 是否关闭连接
		recvRet = recv(clientfd, recvBuf, sizeof(recvBuf), 0);
		if (recvRet == -1)
			cout << "Error receiving data\n";
		else if(strcmp(recvBuf, "y") == 0) {
			cout << "Have closed connection " << clientfd << '.' << endl;
			close(clientfd);
			break;
		}
	}
}

int main() {
	// create socket
	int serverfd = socket(AF_INET, SOCK_STREAM, 0); // IPV4 + TCP
	if(-1 == serverfd) {
		printf("create socket error");
		return -1;
	}
	
	// bind port 
	struct sockaddr_in bindaddr;
	bindaddr.sin_family = AF_INET;
	bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	bindaddr.sin_port = htons(8080);
	if(-1 == bind(serverfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr))) {
		cout << "bind error" <<  endl;
		return -1;
	}
	
	// start listen
	if (listen(serverfd, 1024) == -1) {
		printf("listem error");
		return -1;
	}

	while (1) {
		struct sockaddr_in clientaddr;
		socklen_t clientaddrlen = sizeof(clientaddr);
		
		// accept connection
		int clientfd = accept(serverfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
		if(clientfd!= -1) {
			// 获取客户端的信息
            char cliIp[16];
            inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, cliIp, sizeof(cliIp));
            unsigned short cliPort = ntohs(clientaddr.sin_port);
			cout << "client ip is : " << cliIp << ", port is : " << cliPort << endl;
			
			pid_t pid = fork();
			if(pid == 0) // 子进程
				connectToClient(clientfd);
		}
	}
	
	//close socket
	close(serverfd);
	return 0;
}

  • 26
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是一门linuxc++通讯架构实战课程,针对c/c++语言已经掌握的很熟并希望进一步深造以将来用c++linux下从事网络通讯领域/网络服务器的开发和架构工作。这门课程学习难度颇高但也有着极其优渥的薪水(最少30K月薪,最高可达60-80K月薪),这门课程,会先从nginx源码的分析和讲解开始,逐步开始书写属于自己的高性能服务器框架代码,完善个人代码库,这些,将会是您日后能取得高薪的重要筹码。本课程原计划带着大家逐行写代码,但因为代码实在过于复杂和精细,带着写代码可能会造成每节课至少要4~5小时的超长时,所以老师会在课前先写好代码,主要的时花费在逐行讲解这些代码上,这一点望同学们周知。如果你觉得非要老师领着写代码才行的话,老师会觉得你当前可能学习本门课程会比较吃力,请不要购买本课程,以免听不懂课程并给老师差评,差评也会非常影响老师课程的销售并造成其他同学的误解。 这门课程要求您具备下面的技能:(1)对c/c++语言掌握的非常熟练,语言本身已经不是继续学习的障碍,并不要求您一定熟悉网络或者linux;(2)对网络通讯架构领域有兴趣、勇于挑战这个高难度的开发领域并期望用大量的付出换取高薪;在这门课程中,实现了一个完整的项目,其中包括通讯框架和业务逻辑框架,浓缩总结起来包括如下几点:(1)项目本身是一个极完整的多线程高并发的服务器程序;(2)按照包头包体格式正确的接收客户端发送过来的数据包, 完美解决收包时的数据粘包问题;(3)根据收到的包的不同来执行不同的业务处理逻辑;(4)把业务处理产生的结果数据包正确返回给客户端;本项目用到的主要开发技术和特色包括:(1)epoll高并发通讯技术,用到的触发模式是epoll中的水平触发模式【LT】;(2)自己写了一套线程池来处理业务逻辑,调用适当的业务逻辑处理函数处理业务并返回给客户端处理结果;(3)线程之的同步技术包括互斥量,信号量等等;(4)连接池中连接的延迟回收技术,这是整个项目中的精华技术,极大程度上消除诸多导致服务器程序工作不稳定的因素;(5)专门处理数据发送的一整套数据发送逻辑以及对应的发送线程;(6)其他次要技术,包括信号、日志打印、fork()子进程、守护进程等等;

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值