代码实现
server(服务器端)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
// ./server 192.168.154.128 1234 命令行中打开服务端
// 192.168.154.128是我服务器的ip地址 1234是端口号
int main(int argc,char *argv[]){
//使用socket函数 创建流式套接字
int sockFd = socket(AF_INET,SOCK_STREAM,0);
if(sockFd < 0){
perror("socket err");
return -1;
}
int optval = 1;
//修改不让服务端进入wait_time状态
int ret = setsockopt(sockFd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(int));
struct sockaddr_in addr;
//填充IPv4结构体
//设置地址族为IPv4
addr.sin_family = AF_INET;
//端口占了两个字节 将小端转为大端 也就是命令输入的第三个参数
addr.sin_port = htons(atoi(argv[2]));
//绑定IP地址 命令行输入的第二个参数
addr.sin_addr.s_addr = inet_addr(argv[1]);
//绑定套接字,ip和端口
ret = bind(sockFd,(struct sockaddr *)&addr,sizeof(addr));
if(ret < 0){
perror("bind");
return -1;
}
else{
printf("bind ok\n");
}
//监听 设置监听队列大小为5
//listen函数是用于在服务器端套接字上监听连接请求的操作
ret = listen(sockFd,10);
if(ret < 0){
perror("listen err");
return -1;
}
fd_set rdset; //单纯地去保存就绪的文件描述符
fd_set monitorSet; //使用一个单独的监听集合
FD_ZERO(&monitorSet);
FD_SET(STDIN_FILENO,&monitorSet);
FD_SET(sockFd,&monitorSet);
char buf[4096] = {0};
int netFd = -1;
while(1){
//将监听集合复制给rdset
memcpy(&rdset,&monitorSet,sizeof(fd_set));
//select一直置于阻塞状态,直到监控到文件描述符集合中某个文件描述符发生变化为止
select(20,&rdset,NULL,NULL,NULL);
if(FD_ISSET(STDIN_FILENO,&rdset)){
if(netFd == -1){ //表示目前没有连接
puts("no client is connected!\n");
continue;
}
//否则表示有连接 首先清空缓冲区
bzero(buf,sizeof(buf));
//从标准输入中读取数据,并将其存储在buf数组中。read函数返回实际读取的字节数
ret = read(STDIN_FILENO,buf,sizeof(buf));
//ret == 0时 表示读到终止符 服务器主动断开连接
if(ret == 0){
//发送byebye给客户端
send(netFd,"byebye",6,0);
break;
}
//将数据发给客户端
send(netFd,buf,strlen(buf),0);
}
if(FD_ISSET(sockFd,&rdset)){
//接受客户端的连接请求 并赋值给文件描述符netFd
netFd = accept(sockFd,NULL,NULL);
//加入监听集合
FD_SET(netFd,&monitorSet);
puts("new connect is accept!\n");//表示有连接抵达
}
if(FD_ISSET(netFd,&rdset)){
bzero(buf,sizeof(buf));
//接受客户端发来的数据
ret = recv(netFd,buf,sizeof(buf),0);
//表示客户端主动断开连接 发送byebye给服务端
if(ret == 0){
puts("bye bye");
//将netFd从监听集合移出
FD_CLR(netFd,&monitorSet);
//关闭文件描述符
close(netFd);
netFd = -1;
continue;
}
//将服务端从客户端收到的数据打印出来
puts(buf);
}
}
close(netFd);
//关闭套接字
close(sockFd);
}
代码分析
1.使用socket函数创建一个IPv4的TCP套接字
2.利用bind函数将套接字绑定到指定IP和端口上
3.使用listen函数将套接字设置为监听状态,设置了监听队列的长度为10
4.使用accept函数等待客户端连接请求,并返回连接套接字的文件描述符
5.在一个while循环中循环接收客户端发送的消息,然后将接收到的消息输出到终端
6.关闭套接字
该程序实现了一个简单的TCP服务器的功能,可以接收客户端的连接,并且循环接收客户端发送的消息,将接收到的消息输出到终端
client(客户端)
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
int main(int argc,char *argv[]){
// ./client 192.168.154.128 1234 打开客户端
//使用socket创建流式套接字
int sockFd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
//客户端端需要调用connect()连接服务器,connect和bind的参数形式一致,
//区别在于bind的参数是自己的地址,而connect的参数是对方的地址.
int ret = connect(sockFd,(struct sockaddr *)&addr,sizeof(addr));
if(ret < 0){
perror("connect error!\n");
}
else{
printf("connect ok!\n");
}
fd_set rdset;
char buf[4096] = {0};
while(1){
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO,&rdset);
FD_SET(sockFd,&rdset);
select(sockFd+1,&rdset,NULL,NULL,NULL);
if(FD_ISSET(STDIN_FILENO,&rdset)){
bzero(buf,sizeof(buf));
ret = read(STDIN_FILENO,buf,sizeof(buf));
if(ret == 0){
//客户端主动断开连接
send(sockFd,"byebye",6,0);
break;
}
send(sockFd,buf,strlen(buf),0);
}
if(FD_ISSET(sockFd,&rdset)){
bzero(buf,sizeof(buf));
ret = recv(sockFd,buf,sizeof(buf),0);
//读到终止符 表示服务器断开连接了
if(ret == 0){
puts("chat is end!");
break;
}
puts(buf);
}
}
//关闭套接字
close(sockFd);
}
代码分析
这是一个TCP客户端代码,它的主要功能是连接到指定的服务器,并且有发送消息接收消息的功能
1.socket函数创建了一个流式套接字
2.connect函数表示连接到服务器,这个函数将套接字连接到 sockaddr_in 结构体中描述的IP地址和端口号,如果连接失败,将返回一个负数值
3.read函数从标准输入读取数据,并将其存储在 buf 中
4.send函数将读取的数据发送到服务器
5.close函数关闭套接字
在主循环中,该客户端将不断地从标准输入读取数据并将其发送到服务器,直到程序被强制终止
serverroom(聊天室)
客户端代码不变,仅对服务端稍加修改,就可以达到一个类似聊天室的功能
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
int main(int argc,char *argv[]){
// ./serverroom 192.168.154.128 1234
int sockFd = socket(AF_INET,SOCK_STREAM,0);
int optval = 1;
//修改不让服务端进入wait_time状态
int ret = setsockopt(sockFd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(int));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
ret = bind(sockFd,(struct sockaddr *)&addr,sizeof(addr));
if(ret < 0){
perror("bind");
return -1;
}
else{
printf("bind ok\n");
}
ret = listen(sockFd,10);
fd_set rdset; //单纯地去保存就绪的文件描述符
fd_set monitorSet; //使用一个单独的监听集合
FD_ZERO(&monitorSet);
FD_SET(sockFd,&monitorSet);
char buf[4096] = {0};
int netFdArr[10] = {0};
int curConn = 0;
//1.实现聊天室
//服务器不再监听标准输入
//服务端的功能:1.处理新的客户端连接
//2.处理客户端发送的消息转发
//维护netFd数组 然后进行转发
while(1){
memcpy(&rdset,&monitorSet,sizeof(fd_set));
select(20,&rdset,NULL,NULL,NULL);
//现在只能发送信息不能接收信息了
if(FD_ISSET(sockFd,&rdset)){
netFdArr[curConn] = accept(sockFd,NULL,NULL);
if(netFdArr[curConn] < 0){
perror("accept error!\n");
}
FD_SET(netFdArr[curConn],&monitorSet);
printf("new connect is accept!, curConn = %d\n",curConn);
++curConn;
}
int i;
for(i = 0; i < curConn; ++i){
if(FD_ISSET(netFdArr[i],&rdset)){
bzero(buf,sizeof(buf));
recv(netFdArr[i],buf,sizeof(buf),0);
int j;
for(j = 0; j < curConn; ++j){
if(j == i){
continue;
}
send(netFdArr[j],buf,strlen(buf),0);
}
}
}
}
close(sockFd);
}
结果展示
编译server.c
g++ server.c -o server
编译client.c
g++ client.c -o client
开启服务端
在命令行输入
./server 192.168.154.128 1234
开启客户端
./client 192.168.154.128 1234
聊天室功能展示
以上是TCP实现了简单的服务器客户端通信,谢谢大家观看!