一、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直接控制冰箱,空调,洗衣机等。