七、Linux C/C++TCP 点对点(p2p)传输

一、TCP的点对点传输

点对点传输没有所谓的客户端与服务端,不用listen和accept,只是两个端口的直接连接。
实现点对点传输的步骤如下:

1、 socket();
2、 bind();
3、 connect();
4、send()和recv()

二、代码示例

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/poll.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <pthread.h>

using namespace std;

#define BUFFER_LEN 512
typedef int (*EPOLL_CALLBACK)(int fd);
struct CONNECT_ITEM {
	char rBuffer[BUFFER_LEN] = {0};
	int rLen = 0;
	char wBuffer[BUFFER_LEN] = {0};
	int wLen = 0;

	union {
		EPOLL_CALLBACK recv_callback;
	}recv_t;
	EPOLL_CALLBACK send_callback = nullptr;
}connect_item;

int epfd = 0;

enum _EPOLL_CTRL{
	ADD,
	MOD
};

void setEvent(int fd, EPOLL_EVENTS events, _EPOLL_CTRL ctrl) {
	epoll_event ev;
	ev.events = events; //默认水平触发(LT),有(数据)事件就会一直触发,知道全部处理完
	/*
		EPOLLET为边沿触发(ET),当有事件发生时只触发一次,
		比如来数据了,如果一次没有读完,不会再触发了,所以必须全部读完,在进行下一次epoll_wait
	*/
	//ev.events = EPOLLIN | EPOLLET;
	ev.data.fd = fd;
	epoll_ctl(epfd, ctrl == ADD ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, fd, &ev);
}

int recv_cb(int fd) {
	char* buffer = connect_item.rBuffer;
	int index = connect_item.rLen;

	int count = recv(fd, buffer, BUFFER_LEN, 0);
	
	if (count == 0) {
		printf("disconnect: %d\n", fd);

		epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);		
		close(fd);
		
		return count;
	}else if (count < 0) {
		printf("recv error\n");

		return count;
	}

	printf("RECV===>>> clientfd: %d, count: %d, buffer: %s\n", fd, count, buffer);
	
	return count;
}

int send_cb(int fd) {
	char* buffer = connect_item.wBuffer;
	
	int count = send(fd, buffer, connect_item.wLen, 0);
	printf("send===>>> count: %d, send: %s\n", count, buffer);

	//改变该文件描述符的事件类型为EPOLLIN
	setEvent(fd, EPOLLIN, MOD);

	return count;
}

//绑定本机地址端口
int bind_localAddr(int sockfd, char* ip, uint16_t port) {
	sockaddr_in localAddr;
    memset(&localAddr, 0, sizeof(localAddr));
    localAddr.sin_family = AF_INET;
    localAddr.sin_addr.s_addr = inet_addr(ip);
    localAddr.sin_port = htons(port);

    return bind(sockfd, (sockaddr*)&localAddr, sizeof(localAddr));
}

//连接服务器地址端口
int connect_server(int sockfd, char* ip, uint16_t port) {
	sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = inet_addr(ip);
    serverAddr.sin_port = htons(port);

	return connect(sockfd, (sockaddr*)&serverAddr, sizeof(serverAddr));
}

//等待用户输入发送的数据
void* client_thread(void * arg) {
	int clientfd = *((int*)arg);

    while (1) {
        printf("client > ");
		scanf("%s", connect_item.wBuffer);
		connect_item.wLen = strlen(connect_item.wBuffer);
		if (strcmp(connect_item.wBuffer, "quit") == 0) {
			break;
		}

		//改变该文件描述符的事件类型为EPOLLOUT
		setEvent(clientfd, EPOLLOUT, MOD);
    }

    //释放客户端socket
    close(clientfd);

    return NULL; 
}

int main(int argc, char *argv[]) {
	if (argc <= 2) {
		printf("Usage: %s ip port\n", argv[0]);
		exit(0);
	}

	string serverIP = argv[1];
	uint16_t serverPort = atoi(argv[2]);

    epfd = epoll_create(1); // int size

    int socketfd = socket(AF_INET, SOCK_STREAM, 0);

	//绑定本地地址
    bind_localAddr(socketfd, (char*)"0.0.0.0", serverPort);

	while (1) {
		//一直循环连接对端,这里要注意的是,连接的对端的端口,与本地绑定的端口必须一样,否则连接不上。
		//也就是说想要实现点对点传输,本地绑定的端口必须与对端绑定的端口一样。
		if (0 == connect_server(socketfd, serverIP.data(), serverPort)) {
			printf("connect success!\n");
			break;
		}

		usleep(1);
	}

    connect_item.recv_t.recv_callback = recv_cb;
    connect_item.send_callback = send_cb;
    setEvent(socketfd, EPOLLIN, ADD);

	//创建一个线程用来接收用户输入的数据并发送到connect的对端
	pthread_t thid;
	pthread_create(&thid, NULL, client_thread, &socketfd);

	struct epoll_event events[100000] = {0};
	while (1) {
		int nready = epoll_wait(epfd, events, 100000, -1);

		for (int i = 0; i < nready; i++) {
			int connfd = events[i].data.fd;

			if (connfd == socketfd) {
				if (events[i].events & EPOLLIN) {
					int count = connect_item.recv_t.recv_callback(connfd);
				}else if (events[i].events & EPOLLOUT) {
					connect_item.send_callback(connfd);
				}
			}
		}
	}

    return 0;
}

三、测试步骤

1、在一台电脑上编译并运行起来:

g++ -o ./tcp-p2p tcp-p2p.cpp
./tcp-p2p 192.168.1.21 8888

本机ip比如为192.168.1.20,第二条命令:./tcp-p2p 192.168.1.21 8888,传入的参数分别为要连接的对端的ip的端口。

2、在另一台电脑上编译并运行起来:

g++ -o ./tcp-p2p tcp-p2p.cpp
./tcp-p2p 192.168.1.20 8888

本机ip比如为192.168.1.21,第二条命令:./tcp-p2p 192.168.1.20 8888,传入的参数分别为要连接的对端的ip的端口。

注意:

这里要注意的是,连接的对端的端口,与本地绑定的端口必须一样,否则连接不上。也就是说想要实现点对点传输,本地绑定的端口必须与对端绑定的端口一样。

四、应用场景

可用于家电控制领域,比如迅雷的种子下载、APP直接控制冰箱,空调,洗衣机等。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值