目次
1. マルチスレッドを使用してスレッド化を実装する 2 つのプログラムが互いにチャットします
2. UDP により、複数のプログラムがサーバーとチャットできるようになります
1. マルチスレッドを使用してスレッド化を実装する 2 つのプログラムが互いにチャットします
1. サーバー
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h> // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>
int addr_len ;
int socket_fd = -1 ; // 定义套接字描述符
struct sockaddr_in from_addr ; // 对方的地址 (需要先的到客户端发来的消息我才能放消息出去)
void * send_msg (void * arg)
{
int ret_val = -1 ;
char * msg_to_send = calloc(128 ,1) ;
while (1)
{
fgets(msg_to_send , 128 , stdin);
ret_val = sendto(socket_fd , msg_to_send , strlen(msg_to_send) , 0 ,
(struct sockaddr * )&from_addr, addr_len );
if ( -1 == ret_val )
{
perror("send to error ");
continue ;
}
else{
printf("send succeed %d byte ! \n" , ret_val);
}
}
return 0 ;
}
int main(int argc, char const *argv[])
{
// 创建一个邮箱
socket_fd = socket(AF_INET, SOCK_DGRAM , 0 );
if (-1 == socket_fd)
{
perror("socket error");
return -1 ;
}
// 设置好IP+端口号等信息
// struct sockaddr_in // IPV4地址结构体
// {
// u_short sin_family;// 地址族
// u_short sin_port;// 端口
// struct in_addr sin_addr;// IPV4 地址
// char sin_zero[8];
// };
struct sockaddr_in my_addr = {0} ;
addr_len = sizeof(struct sockaddr_in);
my_addr.sin_family = AF_INET ; // 网际协议 IPV4
my_addr.sin_port = htons(65000); // 设置端口号为65000 并转换为网络字节序
// my_addr.sin_addr.s_addr = inet_addr("192.168.102.2"); // 设置IP地址, 并转换为32位的网络地址
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置绑定当前系统所有的IP地址
// (意思是任意一个地址有数据都可以接收)
// 把设置好的信息与信箱进行帮定
int ret_val = bind(socket_fd , (struct sockaddr *)&my_addr,addr_len);
if (-1 == ret_val)
{
perror("bind error");
return -1 ;
}
else{
printf("bind succeed!\n") ;
}
// 创建一个线程用于发送消息
pthread_t tid ;
ret_val = pthread_create(&tid , NULL , send_msg , NULL );
if (ret_val != 0)
{
fprintf(stderr , "create error :%s" , strerror(ret_val));
close(socket_fd);
return -1 ;
}
char * msg = calloc(128,1);
// 坐等来信
while(1)
{
bzero(msg , 128);
printf("等待消息到达:\n");
// ret_val = read(socket_fd , msg , 128); // 可以直接使用读取函数来读取数据,但是没办法得知数据来自哪里
ret_val = recvfrom(socket_fd , msg , 128 , 0 , (struct sockaddr *)&from_addr, &addr_len );
if (ret_val == -1 )
{
perror("recv error");
continue ;
}
else{
printf("recv succeed \n addr:%s \nport:%d \nmsg : %s \n" ,
inet_ntoa( from_addr.sin_addr ) , // 把二进制的网络字节序的IP地址转换点分十进制的字符串地址
ntohs(from_addr.sin_port) , // 把网络字节序的端口号转换位 本地字节序的短整型
msg );
}
}
// 关闭套接字文件
close(socket_fd);
return 0;
}
2.サーバー
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h> // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>
int addr_len ;
int socket_fd = -1 ; // 定义套接字描述符
struct sockaddr_in from_addr ; // 对方的地址 (需要先的到客户端发来的消息我才能放消息出去)
struct sockaddr_in dest_addr = {0} ;
void * send_msg (void * arg)
{
// 配置 发送的目的地址
addr_len = sizeof(struct sockaddr_in);
dest_addr.sin_family = AF_INET ; // 网际协议 IPV4
dest_addr.sin_port = htons(65000); // 设置端口号为65000 并转换为网络字节序
dest_addr.sin_addr.s_addr = inet_addr("192.168.43.83"); // 设置服务器的IP地址
int ret_val = -1 ;
char * msg_to_send = calloc(128 ,1) ;
while (1)
{
fgets(msg_to_send , 128 , stdin);
ret_val = sendto(socket_fd , msg_to_send , strlen(msg_to_send) , 0 ,
(struct sockaddr * )&dest_addr, addr_len );
if ( -1 == ret_val )
{
perror("send to error ");
continue ;
}
else{
printf("send succeed %d byte ! \n" , ret_val);
}
}
return 0 ;
}
int main(int argc, char const *argv[])
{
// 创建一个邮箱
socket_fd = socket(AF_INET, SOCK_DGRAM , 0 );
if (-1 == socket_fd)
{
perror("socket error");
return -1 ;
}
// 设置好IP+端口号等信息
// struct sockaddr_in // IPV4地址结构体
// {
// u_short sin_family;// 地址族
// u_short sin_port;// 端口
// struct in_addr sin_addr;// IPV4 地址
// char sin_zero[8];
// };
// 创建一个线程用于发送消息
pthread_t tid ;
int ret_val = pthread_create(&tid , NULL , send_msg , NULL );
if (ret_val != 0)
{
fprintf(stderr , "create error :%s" , strerror(ret_val));
close(socket_fd);
return -1 ;
}
char * msg = calloc(128,1);
// 坐等来信
while(1)
{
bzero(msg , 128);
printf("等待消息到达:\n");
// ret_val = read(socket_fd , msg , 128); // 可以直接使用读取函数来读取数据,但是没办法得知数据来自哪里
ret_val = recvfrom(socket_fd , msg , 128 , 0 , (struct sockaddr *)&dest_addr, &addr_len );
if (ret_val == -1 )
{
perror("recv error");
continue ;
}
else{
printf("recv succeed \n addr:%s \nport:%d \nmsg : %s \n" ,
inet_ntoa( dest_addr.sin_addr ) , // 把二进制的网络字节序的IP地址转换点分十进制的字符串地址
ntohs(dest_addr.sin_port) , // 把网络字节序的端口号转换位 本地字节序的短整型
msg );
}
}
// 关闭套接字文件
close(socket_fd);
return 0;
}
2. UDP により、複数のプログラムがサーバーとチャットできるようになります
1. 要件
a. 同一サーバーに複数のクライアントが接続されていると仮定すると、サーバーは接続された複数の接客端末の情報(相手のIPとポート番号)を保存することができます。
b. 指定したクライアントにメッセージを返すことができます (現在のオンライン リストを一覧表示 - ユーザーを選択 - メッセージを送信)
c. 自分でグループ メッセージ送信を実装してみてください (5 つのクライアントがオンラインで、そのうちの 1 つはグループ メッセージを送信する必要があり、サーバーはクライアントがグループ メッセージを送信するのを支援します)。
2.サーバー
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h> // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
int addr_len ;
int socket_fd = -1 ; // 定义套接字描述符
struct sockaddr_in from_addr ; // 对方的地址 (需要先的到客户端发来的消息我才能放消息出去)
struct sockaddr_in arr_addr[10] ; // 全局变量, 属于静态变量, 未初始化时则自动清空为0
int num = 0 ; // 客户端信息数组下标
void * send_msg (void * arg)
{
int ret_val = -1 ;
char * msg_to_send = calloc(128 ,1) ;
while (1)
{
printf("请输入一个 # 显示当前在线列表:\n");
while( getchar() != '#' );
// 显示当前在线列表
printf("**************************************\n");
for (int i = 0; i < num ; i++)
{
printf("arr[%d]:IP:%s\tport:%d\t\n" , i ,
inet_ntoa( arr_addr[i].sin_addr ) , // 把二进制的网络字节序的IP地址转换点分十进制的字符串地址
ntohs(arr_addr[i].sin_port) );// 把网络字节序的端口号转换位 本地字节序的短整型
}
printf("**************************************\n");
printf("*********请选择一个客户端进行回信*********\n");
// 用户输入数组的下标
int i = 0 ;
scanf("%d" , &i) ;
while ( getchar()!= '\n');
printf("*********请输入发送的消息内容*********\n");
fgets(msg_to_send , 128 , stdin);
ret_val = sendto(socket_fd , msg_to_send , strlen(msg_to_send) , 0 ,
(struct sockaddr * )&arr_addr[i], addr_len );
if ( -1 == ret_val )
{
perror("send to error ");
continue ;
}
else{
printf("send succeed %d byte ! \n" , ret_val);
}
}
return 0 ;
}
bool if_access( struct sockaddr_in new_addr )
{
for (int i = 0; i < num ; i++)
{
// 如果内存内容一致则返回 0
if(memcmp(&arr_addr[i] , &new_addr , sizeof(struct sockaddr_in)))
{
continue ;
}
else
{
return true ; // 如果返回值为0 表示已经存在, 因此直接返回true
}
}
return false ;
}
int main(int argc, char const *argv[])
{
// 创建一个邮箱
socket_fd = socket(AF_INET, SOCK_DGRAM , 0 );
if (-1 == socket_fd)
{
perror("socket error");
return -1 ;
}
// 设置好IP+端口号等信息
// struct sockaddr_in // IPV4地址结构体
// {
// u_short sin_family;// 地址族
// u_short sin_port;// 端口
// struct in_addr sin_addr;// IPV4 地址
// char sin_zero[8];
// };
struct sockaddr_in my_addr = {0} ;
addr_len = sizeof(struct sockaddr_in);
my_addr.sin_family = AF_INET ; // 网际协议 IPV4
my_addr.sin_port = htons(65000); // 设置端口号为65000 并转换为网络字节序
// my_addr.sin_addr.s_addr = inet_addr("192.168.102.2"); // 设置IP地址, 并转换为32位的网络地址
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置绑定当前系统所有的IP地址
// (意思是任意一个地址有数据都可以接收)
// 把设置好的信息与信箱进行帮定
int ret_val = bind(socket_fd , (struct sockaddr *)&my_addr,addr_len);
if (-1 == ret_val)
{
perror("bind error");
return -1 ;
}
else{
printf("bind succeed!\n") ;
}
// 创建一个线程用于发送消息
pthread_t tid ;
ret_val = pthread_create(&tid , NULL , send_msg , NULL );
if (ret_val != 0)
{
fprintf(stderr , "create error :%s" , strerror(ret_val));
close(socket_fd);
return -1 ;
}
char * msg = calloc(128,1);
// 坐等来信
while(1)
{
bzero(msg , 128);
printf("等待消息到达:\n");
// ret_val = read(socket_fd , msg , 128); // 可以直接使用读取函数来读取数据,但是没办法得知数据来自哪里
ret_val = recvfrom(socket_fd , msg , 128 , 0 , (struct sockaddr *)&from_addr, &addr_len );
if (ret_val == -1 )
{
perror("recv error");
continue ;
}
else{
printf("recv succeed \n addr:%s \nport:%d \nmsg : %s \n" ,
inet_ntoa( from_addr.sin_addr ) , // 把二进制的网络字节序的IP地址转换点分十进制的字符串地址
ntohs(from_addr.sin_port) , // 把网络字节序的端口号转换位 本地字节序的短整型
msg );
if(!if_access(from_addr) && num < 9 )
{
arr_addr[num] = from_addr ; // 把当前的客户端的保存到数组中
num ++ ; // 数组下标+1
}
for (int i = 0; i < num ; i++)
{
printf("arr[%d]:IP:%s\tport:%d\t\n" , i ,
inet_ntoa( arr_addr[i].sin_addr ) , // 把二进制的网络字节序的IP地址转换点分十进制的字符串地址
ntohs(arr_addr[i].sin_port) );// 把网络字节序的端口号转换位 本地字节序的短整型
}
}
}
// 关闭套接字文件
close(socket_fd);
return 0;
}
3. クライアント
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h> // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>
int addr_len ;
int socket_fd = -1 ; // 定义套接字描述符
struct sockaddr_in from_addr ; // 对方的地址 (需要先的到客户端发来的消息我才能放消息出去)
struct sockaddr_in dest_addr = {0} ;
void * send_msg (void * arg)
{
// 配置 发送的目的地址
addr_len = sizeof(struct sockaddr_in);
dest_addr.sin_family = AF_INET ; // 网际协议 IPV4
dest_addr.sin_port = htons(65000); // 设置端口号为65000 并转换为网络字节序
dest_addr.sin_addr.s_addr = inet_addr("192.168.102.2"); // 设置服务器的IP地址
int ret_val = -1 ;
char * msg_to_send = calloc(128 ,1) ;
while (1)
{
fgets(msg_to_send , 128 , stdin);
ret_val = sendto(socket_fd , msg_to_send , strlen(msg_to_send) , 0 ,
(struct sockaddr * )&dest_addr, addr_len );
if ( -1 == ret_val )
{
perror("send to error ");
continue ;
}
else{
printf("send succeed %d byte ! \n" , ret_val);
}
}
return 0 ;
}
int main(int argc, char const *argv[])
{
// 创建一个邮箱
socket_fd = socket(AF_INET, SOCK_DGRAM , 0 );
if (-1 == socket_fd)
{
perror("socket error");
return -1 ;
}
// 设置好IP+端口号等信息
// struct sockaddr_in // IPV4地址结构体
// {
// u_short sin_family;// 地址族
// u_short sin_port;// 端口
// struct in_addr sin_addr;// IPV4 地址
// char sin_zero[8];
// };
// 创建一个线程用于发送消息
pthread_t tid ;
int ret_val = pthread_create(&tid , NULL , send_msg , NULL );
if (ret_val != 0)
{
fprintf(stderr , "create error :%s" , strerror(ret_val));
close(socket_fd);
return -1 ;
}
char * msg = calloc(128,1);
// 坐等来信
while(1)
{
bzero(msg , 128);
printf("等待消息到达:\n");
// ret_val = read(socket_fd , msg , 128); // 可以直接使用读取函数来读取数据,但是没办法得知数据来自哪里
ret_val = recvfrom(socket_fd , msg , 128 , 0 , (struct sockaddr *)&dest_addr, &addr_len );
if (ret_val == -1 )
{
perror("recv error");
continue ;
}
else{
printf("recv succeed \n addr:%s \nport:%d \nmsg : %s \n" ,
inet_ntoa( dest_addr.sin_addr ) , // 把二进制的网络字节序的IP地址转换点分十进制的字符串地址
ntohs(dest_addr.sin_port) , // 把网络字节序的端口号转换位 本地字节序的短整型
msg );
}
}
// 关闭套接字文件
close(socket_fd);
return 0;
}