一.修改后的代码以及程序运行结果
错误分析放在第二部分,只需要修改后的程序代码只看第一部分即可。
1、创建并打开服务器程序与客户端程序,观察结果
源程序:
创建tcpthreadserver.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
#include <arpa/inet.h>
#define LinkNum 5 //连接数
int client_sockfd[LinkNum]; /*分别记录服务器端的套接字 与连接的多个客户端的套接字*/
int server_sockfd = -1;//命名套接字
int curLink=0; //当前连接数
sem_t mutex; //表示连接数的资源信号量
char stopmsg[100]; //服务器端发送消息缓冲区
pthread_t thread;
void quit()
{//客户服务通信结束处理函数
int i;
char*msg="服务器将要关闭了!";
while(1)
{
if(strcmp(stopmsg,"quit")==0)
{//如果服务器端发送消息为"quit",则提示服务器将关闭
printf("服务器关闭!\n");
for(i=0;i<LinkNum;i++)
if(client_sockfd[i]!=-1)
write(client_sockfd[i], msg, sizeof(msg)); /*依次向继续保持连接的客户端发出“服务器将关闭”的通知消息*/
close(server_sockfd); //关闭服务器监听套接字
sem_destroy(&mutex); //销毁连接数资源信号量mutex
exit(0);
}
}
}
void rcv_snd(int n)
{//服务器与客户端的收发通信函数,n为连接数组序号
int i=0;
int retval;
char recv_buf[1024]; //接收消息缓冲区
char send_buf[1024]; //发送消息缓冲区
int client_len = 0;
int rcv_num; //从客户端接收到的消息长度
pthread_t tid; //线程id
tid = pthread_self(); //获取当前线程id
printf("-----------服务器线程id=%lu使用套接字%d,n=%d与客户机对 话开始...\n",tid,client_sockfd[n],n);
do
{//服务器与客户端循环发送接收消息
memset(recv_buf, 0, 1024);//接收消息缓冲区清零
printf("服务器线程id=%lu,套接字%d,n=%d等待客户端回应...\n",tid,client_sockfd[n],n);
rcv_num = read(client_sockfd[n], recv_buf,sizeof(recv_buf));
printf("服务器线程id=%lu,套接字%d,n=%d从客户端接受的消息长度=%zu\n",tid,client_sockfd[n],n,strlen(recv_buf));
printf("3.服务器线程id=%lu,套接字%d,n=%d<---客户端,服务器从客户端接受的消息是:(%d) :%s\n",tid,client_sockfd[n],n,rcv_num, recv_buf);
if(rcv_num==0) break;
sleep(1);
if(strncmp(recv_buf,"!q",2)==0) break; //若 接收到"!q",则结束循环,通信结束
printf("4.服务器线程id=%lu,套接字%d,n=%d--->客户端,请输入服务器要发送给客户机的消息:",tid,client_sockfd[n],n);
memset(send_buf, 0, 1024); //发送消息缓冲区清零
scanf("%s",send_buf); //服务器端键盘输入字符串消息,输入"!q"或"quit",则通信结束
strcpy(stopmsg,send_buf);
write(client_sockfd[n], send_buf, sizeof(send_buf));
if(strncmp(send_buf,"!q",2)==0) break; //若服务器端发送"!q",则结束循环,通信结束
if(strncmp(send_buf,"quit",4)==0) break; //若服务器端发送"quit",则结束循环,通信结束
}while(strncmp(recv_buf,"!q",2)!=0);
printf("-----------服务器线程id=%lu,套接字%d,n=%d与客户机对话结束---------\n",tid,client_sockfd[n],n);
close(client_sockfd[n]); //关闭连接套接字
client_sockfd[n]=-1; //被关闭连接套接字数组项置 为空闲
curLink--;//当前连接数减1
printf("当前连接数为:%d(<=%d)\n",curLink,LinkNum);//输出当前连接数和最大连接数
sem_post(&mutex);//释放可用连接数资源信号量mutex
pthread_exit(&retval); //当前服务器线程结束
}
int main(void)
{
char recv_buf[1024];//接收消息缓冲区
char send_buf[1024]; //发送消息缓冲区
int client_len = 0;
struct sockaddr_in server_addr;//服务器端协议地址
struct sockaddr_in client_addr; //客户端协议地址
int i=0;//连接套接字数组循环变量
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
server_addr.sin_family = AF_INET; //指定网络套接字
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //接受所有IP 地址的连接
server_addr.sin_port = htons(9736); //绑定到9736端口
bind(server_sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));//协议套接字命名为server_sockfd
printf("1、服务器开始listen...\n");
listen(server_sockfd, LinkNum); /*创建连接数最大为LinkNum的 套接字队列,监听命名套接字,listen不会阻塞,它向内核报告套接字和最大连接数*/
signal(SIGCHLD, SIG_IGN); //忽略子进程停止或退出信号
printf("输入!q,服务结束.\n");//输入!q,服务结束
pthread_create(&thread,NULL,(void*)(&quit),NULL);//创建线程, 执行函数quit
for(i=0;i<LinkNum;i++) client_sockfd[i]=-1;//初始化连接队列
sem_init(&mutex,0,LinkNum); //信号量mutex初始化为连接数
while(1)
{
for(i=0;i<LinkNum;i++) //搜寻空闲连接
if(client_sockfd[i]==-1) break;
if(i==LinkNum)
{//如果达到最大连接数,则客户等待
printf("已经达到最大连接数%d,请等待其它客户释放连接...\n",LinkNum);
sem_wait(&mutex); //阻塞等待空闲连接
continue; //被唤醒后继续监测是否有空闲连接
}
client_len = sizeof(client_addr);
printf("2、服务器开始accept...i=%d\n",i);
client_sockfd[i] = accept(server_sockfd, (struct sockaddr*)&client_addr, &client_len);
curLink++;//当前连接数增1
sem_wait(&mutex); //可用连接数信号量mutex减1
printf("当前连接数为:%d(<=%d)\n",curLink,LinkNum);
printf("连接来自:连接套接字号=%d,IP地址=%s,端口号=%d\n",client_sockfd[i],inet_ntoa(client_addr.sin_addr),ntohs( client_addr.sin_port));//输出客户端地址信息
pthread_create(malloc(sizeof(pthread_t)),NULL,(void*)(&rcv_snd ),(void*)i);
}
}
创建tcpclient.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
int main(void)
{
int sockfd; //客户端套接字描述符
int len = 0;
struct sockaddr_in address; //套接字协议地址
char snd_buf[1024]; //发送消息缓冲区
char rcv_buf[1024]; //接收消息缓冲区
int result;
int rcv_num;//接收消息长度
pid_t cpid;//客户进程标识符
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("客户端创建套接字失败!\n");
return 1;
}
address.sin_family = AF_INET; //使用网络套接字
address.sin_addr.s_addr = inet_addr("127.0.0.1");//服务器地址
address.sin_port = htons(9736); //服务器所监听的端口
if(inet_aton("127.0.0.1",&address.sin_addr)<0)
{
printf("inet_aton error.\n");
return -1;
}
len = sizeof(address);
cpid=getpid(); //获取客户进程标识符
printf("1、客户机%d开始connect服务器...\n",cpid);
result = connect(sockfd, (struct sockaddr*)&address, len);
if(result == -1)
{
perror("客户机connect服务器失败!\n");
exit(1);
}
printf("-----------客户机%d与服务器线程对话开始...\n",cpid);
do
{//客户机与服务器循环发送接收消息
printf("2.客户机%d--->服务器:sockfd=%d,请输入客户机要发送给服务器的消息:",cpid,sockfd);
memset(snd_buf, 0, 1024); //发送缓冲区清零
scanf("%s",snd_buf); //键盘输入欲发送给服务器的消息字符串
write(sockfd, snd_buf, sizeof(snd_buf)); //将消息发送到套接字
if(strncmp(snd_buf,"!q",2)==0) break;//若发送"!q",则结束循环, 通信结束
memset(rcv_buf, 0, 1024); //接收缓冲区清零
printf("客户机%d,sockfd=%d 等待服务器回应...\n",cpid,sockfd);
rcv_num = read(sockfd, rcv_buf, sizeof(rcv_buf));
printf("客户机%d,sockfd=%d 从服务器接收的消息长度=%ld\n",cpid,sockfd,strlen(rcv_buf));
printf("3.客户机%d<---服务器:sockfd=%d,客户机从服务器接收到的消息是: (%d) :%s\n", cpid,sockfd,rcv_num, rcv_buf); //输出客户机从服务器接收的消息
sleep(1);
if(strncmp(rcv_buf,"quit",4)==0) break; //如果收到"quit", 则结束循环,通信结束
}while(strncmp(rcv_buf,"!q",2)!=0); //如果收到"!q",则结束循环, 通信结束
printf("-----------客户机%d,sockfd=%d 与服务器线程对话结束--- ------\n",cpid,sockfd);
close(sockfd); //关闭客户机套接字
}
编译链接命令:
gcc tcpthreadserver.c -o tcpthreadserver -lpthread
gcc tcpclient.c -o tcpclient
运行命令:
./tcpthreadserver
./tcpclient
交互与结果:
多客户端运行结果
二.问题分析
代码输入后,编译出现问题,报错显示thread未定义,翻看讲义后也未找到,故自己增加了
pthread_t thread;
然后可以成功编译
分别打开客户端与服务端,建立连接时报错,显示段错误(核心已转储)
在网上查询后,尝试用gdb分析错误
一般核心数据大小为0,不能直接使用ulimit -c unlimited指令修改,该指令只在当前终端关闭后就会失效,需要改/etc/security/limits.conf的数据,在底部加入soft core unlimited与hard core unlimited
同时使用sudo systemctl disable apport.service或者 sudo service apport stop ,关闭错误报告功能,重启虚拟机。
此时输入cat /proc/sys/kernel/core_pattern 可以查询到核心文件
此时再次启动两个终端,先后启动服务端与客户端,再次报错后,会出现一个core包
此时利用gdb分析,格式是gdb 程序路径 核心路径
同时输入bt回溯定位,便于找到相关报告
根据错误信息,它指示程序在执行时遇到了一个名为 __strlen_evex 的函数,该函数定义在 strlen-evex.S 文件中的第 77 行。然而,系统无法找到该文件或目录。
根据搜索的结果,只能找到这个目录是包含了与 x86_64 架构相关的多架构支持文件
尝试在电脑里搜索该文件夹,sudo find / -type d -name multiarch
即使处于管理员权限状态,还是显示权限不够,这条路走死了。
换个思路,从创建可执行文件的报错着手。必须解决的错误解决了,从各个警报入手。
这个警告是由于格式字符串中的格式指示符 %u 期望的是 unsigned int 类型的参数,但实际上传递给 printf 函数的第二个参数 tid 是 pthread_t 类型(在此情况下是 long unsigned int)。这种类型不匹配导致了警告。故将%u改为%lu。
警告50,52,53,57,65也是一样的错误,同样的方法修改,不再赘述。
这时可以选择编译并运行
仍然报错
这个警告是由于格式字符串中的格式指示符 %d 期望的是 int 类型的参数,但实际上传递给 printf 函数的第五个参数 strlen(recv_buf) 是 size_t 类型(在此情况下是 long unsigned int)。这种类型不匹配导致了警告。
在先前错误显示与strlen函数的调用有关,大概率是这个函数出问题
size_t 类型的参数,改用 %zu 格式指示符
再次报错。
这个警告是因为在代码中使用了未声明的函数 inet_ntoa。编译器发出警告,因为它无法确定该函数的定义和返回类型。
inet_ntoa 函数用于将网络字节序的 IP 地址转换为点分十进制表示的字符串形式。要解决这个警告,需要包含 <arpa/inet.h> 头文件,该头文件声明了 inet_ntoa 函数。
首部添加 #include <arpa/inet.h>
连接正常
此时还有最后一个警告
这个警告是因为在代码中进行了从整数类型到指针类型的强制转换。编译器发出警告,因为整数类型和指针类型的大小可能不同,可能导致错误的内存访问,应该使用正确的类型进行转换,以确保类型匹配。
pthread_create(malloc(sizeof(pthread_t)),NULL,(void*)(&rcv_snd ),(void*)i);
应将原代码改为
pthread_t *thread = malloc(sizeof(pthread_t));
pthread_create(thread, NULL, rcv_snd, (void*)i);
但是更改后产生了新警告
这个警告是由于在调用 pthread_create 函数时,传递给第三个参数 rcv_snd 的函数指针类型与 pthread_create 期望的类型不匹配。
根据错误消息,rcv_snd 函数的类型为 void (*)(int),但是 pthread_create 函数期望的是 void* (*)(void*) 类型的函数指针。再修改过于麻烦,保留原代码。(事实证明,能运行就别改,越改越抽象)
同样道理,修改客户端的警告。
最终生成效果
最终运行效果
同时连接两个客户端也正常运行
理论上的根本问题是,使用了 inet_ntoa 函数,但是没有添加 <arpa/inet.h> 头文件
需要在首部添加#include <arpa/inet.h>