内容有:
- 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;
}
}