综合网络编程知识,实现一个相当于自定义的简单通信协议,类似飞秋的通信

内容有:

  • 1.上线告诉所有人在线情况。(广播)
  • 2.接收到别人上线的广播,并告诉对方我在线上。
  • 3.再告诉对方我的TCP端口号和服务器地址,让对方来连接我。
  • 4.如果是我广播后接收到对方的在线单播,那么继续等待对方发TCP端口号和服务器地址。

效果演示:(有不完善之处,例如没有退出登录,但是可以实现,可以利用signal实现!!)

在这里插入图片描述

/********************************************************************
File Name: net.h
Author: xiening
Mail: 1606598696@qq.com
Created Time: 2020年10月17日 星期六 16时38分09秒
*******************************************************************/

#ifndef NET_H
#define NET_H 

#include <sys/types.h>          
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>

#define UDP_PORT 50001
#define TCP_PORT 50002
#define TCP_PORT1 50003


#define ON_Broad 	1
#define ON_Line 	2

typedef struct ONlINELIST
{
	struct sockaddr_in addr;
	struct ONlINELIST* next; 
}OnlineList, *POnlineList;

int udp_sockfd;
int tcp_sockfd;
//int tcp_sockfd1;

int istcp;

//在线用户链表
POnlineList head;

//添加在线用户:
void Add_Online(POnlineList old, struct sockaddr_in new);

//udp初始化:
int udp_init();

//udp 广播:
void udp_broadcast();

//udp 接受数据:
void *udp_recv(void *arg);
void udp_send();

//遍历打印在线用户:
void showonline();

//初始化tcp
void *tcp_init(int port);

//tcp 接收数据
void *tcp_recv(void *arg);

//tcp 连接:
void *tcp_connect(struct sockaddr_in *destaddr, int port);

enum name{
	show,
	tcpconnect,
	Default

};


#endif

/********************************************************************
File Name: main.c
Author: xiening
Mail: 1606598696@qq.com
Created Time: 2020年10月13日 星期二 15时38分36秒
*******************************************************************/

#include "net.h"

int main(int argc , char const *argv[])
{
	head = malloc(sizeof(OnlineList));
	head->next = NULL;

	istcp = false;
	
	int ret = udp_init();
	if(ret == -1)
	{
		printf("udp_init failed\n");
		return -1;
	}

	udp_broadcast();


	pthread_t TID;
	pthread_create(&TID, NULL, udp_recv, NULL);

/*
	
	pthread_t TID1;
	pthread_create(&TID1, NULL, tcp_init, NULL);
*/
	
	enum name num;
	char buf[32];

	while(1)
	{
		while(istcp)
		{
			sleep(1);
		}
		bzero(buf, 32);
		num = Default;
		fgets(buf, 32, stdin);
		if(strcmp(buf, "show\n") == 0)
			num = show;
		if(strcmp(buf, "tcpconnect\n") == 0)
			num = tcpconnect;
		switch(num)
		{
			case show:
				showonline();//打印在线用户
				break;
			case tcpconnect:
				//tcp_connect();
				udp_send();
				break;
			default:
				printf("请输入正确的指令\nusage:show or tcpconnect\n");
				break;
		}
	}

    return 0;
}

/********************************************************************
File Name: src/net.c
Author: xiening
Mail: 1606598696@qq.com
Created Time: 2020年10月17日 星期六 20时50分02秒
*******************************************************************/

#include "net.h"

//tcp初始化:
void *tcp_init(int port)
{
	tcp_sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(tcp_sockfd == -1)
	{
		fprintf(stderr, "Created tcp socket failed:%s\n", strerror(errno));
		return NULL;
	}


	int optval = 1;
	int optlen = sizeof(int);
	int ret = setsockopt(tcp_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, optlen);
	if(ret == -1)
	{
		fprintf(stderr, "setsockopt failed :%s\n", strerror(errno));
		return NULL;
	}


	struct sockaddr_in myaddr;
	myaddr.sin_family = AF_INET;
	myaddr.sin_port = htons(port);
	myaddr.sin_addr.s_addr = inet_addr("0.0.0.0");
	ret = bind(tcp_sockfd, (struct sockaddr*)&myaddr,
					sizeof(struct sockaddr));
	if(ret == -1)
	{
		fprintf(stderr, "%s-%d: bind failed:%s\n", 
				__FUNCTION__, __LINE__, strerror(errno));
		return NULL;
	}
	else
	{
		printf("bind succeed\n");
	}

	//监听套接字:(单次)
	ret = listen(tcp_sockfd, 1);//同时可以监听1个
	if(ret == -1)
	{
		fprintf(stderr, "listen failed:%s\n", strerror(errno));
		return NULL;
	}

	struct sockaddr_in cliAddr;
	//等待连接:
	int len = sizeof(struct sockaddr);
	
	/*
	//将接受信息设为信号异步驱动:
	signal(SIGIO, tcp_recv);
	fcntl(tcp_sockfd1, F_SETOWN, getpid()); //设置当前进程号的进程为信号驱动
	fcntl(tcp_sockfd1, F_SETFL, O_ASYNC); //设置SIGIO为异步驱动
*/

//	while(1)
//	{
		int tcp_sockfd1 = accept(tcp_sockfd, (struct sockaddr*)&cliAddr, &len);
		if(tcp_sockfd == -1)
		{
			fprintf(stderr, "accept failed:%s\n", strerror(errno));
			return NULL;
		}

		
		pthread_t tid;
		pthread_create(&tid, NULL, tcp_recv, (void*)&tcp_sockfd1);
		
//		tcp_recv(&tcp_sockfd1);		
	
		

//		}

	//发送数据
	char buf[128];
	printf("你正在连接的是:%s 请开始你们的交流吧!\n", 
				inet_ntoa(cliAddr.sin_addr));
	while(1)
	{
		bzero(buf, 128);
        printf("我:");
        fgets(buf, 128, stdin);
        ret = send(tcp_sockfd1, buf, 128, 0);
        if(-1 == ret)
        {
            perror("send error\n");
			close(tcp_sockfd);
			return NULL;
        }
		if(!strcmp(buf, "88\n"))
		{
			printf("您断开了连接,通信结束\n");
			close(tcp_sockfd);
			return NULL;
		}


	}

}

//tcp 接收数据:
void *tcp_recv(void *arg)
{
	int tcp_sockfd1 =*((int*)arg);

	char buf[1024];
	while(1)
	{
		bzero(buf, 1024);
		int ret = recv(tcp_sockfd1, buf, 1024, 0);
		if(ret <= 0)
		{
			//直接断开,可能是我主动断开了tcp
			istcp =false;
			break;
		}
		printf("他:%s", buf);
		if(!strcmp(buf, "88\n"))
		{
			printf("对方断开了连接,通信结束\n");
			istcp = false;
			close(tcp_sockfd1);
			break;
		}

	}
	close(tcp_sockfd1);

	pthread_exit(NULL);


}

//tcp 连接:
void *tcp_connect(struct sockaddr_in *destaddr, int port)
{
	int tcp_sockfd1 = socket(AF_INET, SOCK_STREAM, 0);
	if(tcp_sockfd1 == -1)
	{
		fprintf(stderr, "Created tcp socket failed:%s\n", strerror(errno));
		istcp = false;
		return NULL;
	}

	int optval = 1;
	int optlen = sizeof(int);
	int ret = setsockopt(tcp_sockfd1, SOL_SOCKET, SO_REUSEADDR, &optval, optlen);
	if(ret == -1)
	{
		fprintf(stderr, "setsockopt failed :%s\n", strerror(errno));
		istcp = false;
		return NULL;
	}


	struct sockaddr_in myaddr;
	myaddr.sin_family = AF_INET;
	myaddr.sin_port = htons(TCP_PORT1);
	myaddr.sin_addr.s_addr = inet_addr("0.0.0.0");
	ret = bind(tcp_sockfd1, (struct sockaddr*)&myaddr,
					sizeof(struct sockaddr));
	if(ret == -1)
	{
		fprintf(stderr, "%s-%d: bind failed:%s\n", 
				__FUNCTION__, __LINE__, strerror(errno));
		istcp = false;
		return NULL;
	}
	else
	{
		printf("bind succeed\n");
	}



	//请求连接:
	struct sockaddr_in srcAddr;
	srcAddr.sin_family = AF_INET;
	srcAddr.sin_port = htons(port);
	srcAddr.sin_addr.s_addr = destaddr->sin_addr.s_addr;
	int len = sizeof(struct sockaddr);
	ret  = connect(tcp_sockfd1, (struct sockaddr*)&srcAddr, len);
	if(ret == -1)
	{
		fprintf(stderr, "connect failed:%s\n", strerror(errno));
		istcp = false;
		return NULL;
	}
	else 
	{
		printf("connect succeed\n");
	}

	//循环来信
	
	pthread_t tid;
	pthread_create(&tid, NULL, tcp_recv, (void*)&tcp_sockfd1);
	
	
	printf("您正在和 ip:%s 连接,请开始交流吧\n", 
				inet_ntoa(destaddr->sin_addr));
	
	char buf[128];
	while(1)
	{
		bzero(buf, 128);
		printf("我:");
		fgets(buf, 128, stdin);
		ret = send(tcp_sockfd1, buf, 128, 0);
		if(ret == -1)
		{
			printf("send failed\n");
			istcp = false;
			return NULL;
		}
		if(!strcmp(buf,"88\n"))
		{
			printf("您已断开了连接,通信结束\n");
			istcp = false;
			close(tcp_sockfd1);
			break;
		}
		
	}

}

//udp初始化:
int udp_init()
{
	udp_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(udp_sockfd == -1)
	{
		fprintf(stderr, "Created udp socket failed:%s\n", strerror(errno));
		return -1;
	}

	int optval = 1;
	int optlen = sizeof(int);
	int ret = setsockopt(udp_sockfd, SOL_SOCKET, SO_BROADCAST, &optval, optlen);
	if(ret == -1)
	{
		fprintf(stderr, "setsockopt failed :%s\n", strerror(errno));
		return -1;
	}

	optval = 1;
	optlen = sizeof(int);
	ret = setsockopt(udp_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, optlen);
	if(ret == -1)
	{
		fprintf(stderr, "setsockopt failed :%s\n", strerror(errno));
		return -1;
	}


	struct sockaddr_in myaddr;
	myaddr.sin_family = AF_INET;
	myaddr.sin_port = htons(UDP_PORT);
	myaddr.sin_addr.s_addr = inet_addr("0.0.0.0");
	ret = bind(udp_sockfd, (struct sockaddr*)&myaddr,
					sizeof(struct sockaddr));
	if(ret == -1)
	{
		fprintf(stderr, "bind failed:%s\n", strerror(errno));
		return -1;
	}


}

//udp 广播
void udp_broadcast()
{
	struct sockaddr_in destaddr;
	destaddr.sin_family = AF_INET;
	destaddr.sin_port = htons(UDP_PORT);
	destaddr.sin_addr.s_addr = inet_addr("255.255.255.255");

	//广播该局域网内:“1”
	char buf[32];
	sprintf(buf, "%d", ON_Broad);
	sendto(udp_sockfd, buf, 32, 0,
				(struct sockaddr*)&destaddr,
				sizeof(struct sockaddr));


}


//udp 接收数据
void *udp_recv(void *arg)
{
	struct sockaddr_in destaddr;
	char buf[32];
	int len = sizeof(struct sockaddr);
	while(1)
	{
		bzero(buf, 32);
		recvfrom(udp_sockfd, buf, 32, 0,
				(struct sockaddr*)&destaddr, &len);
//		if(!strcmp(inet_ntoa(destaddr.sin_addr), "192.168.137.63"))
//			continue;

		//接受新上线发来的广播信息“1”
		if(strcmp(buf, "1") == 0)
		{
			printf("[ip:%s] : ON_Broad:[%s]\n",
					inet_ntoa(destaddr.sin_addr),
					buf);

			//并回传他我在线上
			bzero(buf, 32);
			sprintf(buf, "%d", ON_Line);
			sendto(udp_sockfd, buf, 32, 0,
						(struct sockaddr*)&destaddr,
						sizeof(struct sockaddr));
			bzero(buf, 32);
			//添加在线用户链表:
			Add_Online(head, destaddr);
		}

		//接受在线成员发来的信息:“2”
		else if(strcmp(buf, "2") == 0)
		{
			printf("[ip:%s] : ON_Line:[%s]\n",
					inet_ntoa(destaddr.sin_addr),
										buf);
			//添加在线用户链表:
			Add_Online(head, destaddr);
		}
		//接受对面发来的端口号和服务器地址
		else if(strlen(buf) > 10)
		{
			printf("%s\n", buf);
			char protocol[32];
			char ip[32];
			int port;
			sscanf(buf, "%[^-]-%[^-]-%d", protocol, ip, &port);
			printf("protocol:%s ip:%s port:%d\n", protocol, ip, port);
			//使得主线程进入sleep,暂停的目的是想发送和接收信息
			istcp = true;
			tcp_connect(&destaddr, port);
			//因为会接收到单播2次,所以忽略掉一次
			bzero(buf, 32);
			recvfrom(udp_sockfd, buf, 32, 0,
					(struct sockaddr*)&destaddr, &len);

		}
	}

	pthread_exit(NULL);


}

void udp_send()
{

	char buf[128];
	printf("请输入想要连接的ip地址和你开启的tcp端口号:\nusage:xxx.xxx.xxx.xxx-xxxxx\n");
	
	scanf("%s", buf);
	char ip[32];
	int port;
	sscanf(buf, "%[^-]-%d", ip, &port);
	/*if(strlen(ip) != 15)
	{
		printf("请输入正确的ip地址\n");
		return ;
	}
	*/
	if(port == UDP_PORT)
	{
		printf("该端口号已经被udp占用,请输入其它端口号\n");
		return ;
	}
	//遍历链表中是否存在该地址:
	POnlineList tmp = head;
	while(tmp->next != NULL)
	{
		tmp = tmp->next;
		if(strcmp(ip, inet_ntoa(tmp->addr.sin_addr)) == 0)
		{
			//打包字符串:
			bzero(buf, 128);
			sprintf(buf, "tcpconnect-%s-%d", ip, port);
			sendto(udp_sockfd, buf, 128, 0,
					(struct sockaddr*)&(tmp->addr),
					sizeof(struct sockaddr));
			printf("send succeed : [%s]\n", buf);
			//初始化tcp:
			tcp_init(port);
			return;

		}

	}
	//没有匹配项:
	printf("该 ip:%s 不在登录链表或者非法\n", ip);
	return ;


}


//添加在线用户:
void Add_Online(POnlineList old, struct sockaddr_in new)
{
	POnlineList tmp = old->next;
	POnlineList tmp1 = old;

	//printf("addr: %s\n", inet_ntoa(new.sin_addr));	
	char buf[32];
	strcpy(buf, inet_ntoa(new.sin_addr));

	//遍历链表到末尾:
	while(tmp != NULL)
	{
		//相同地址的排除
		//printf("addr1: %s\n", inet_ntoa(tmp->addr.sin_addr));
		char buf1[32];
		strcpy(buf1, inet_ntoa(tmp->addr.sin_addr));

		if(strcmp(buf, buf1) == 0)
			return ;
		//tmp记录tmp的前一个节点
		tmp1 = tmp;
		tmp = tmp->next;

	}
	//插入到末尾:
	if(tmp == NULL)
	{
		tmp = malloc(sizeof(OnlineList));
		tmp->addr = new;
		tmp->next = NULL;
		tmp1->next = tmp;
	}



}

//遍历打印在线用户:
void showonline()
{
	POnlineList tmp = head->next;
	printf("在线的用户有:\n");
	while(tmp != NULL)
	{
		printf("%s\n", inet_ntoa(tmp->addr.sin_addr));
		tmp = tmp->next;
	}
}


  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值