Linux-C TCP简单例子
Linux-C TCP简单例子
一、简述
记-使用TCP协议通信的简单例子。
例子1:一个客户端,一个服务端,客户端发送信息,服务端就收信息。
例子2:使用多线程实现 服务器与客户端的双向通信。
例子3:使用多路复用实现 服务器与客户端的双向通信 。
例子4:多个客户端,一个服务器,客户端发送信息,服务端就收信息。
例子5:使用多线程实现 服务器与多个客户端的双向通信。
例子6:使用多路复用实现 服务器与多个客户端的双向通信。
代码打包:链接: https://pan.baidu.com/s/1x-JnmAm-4yqu54SoUqr20A 提取码: sxt5
二、TCP基本操作。
说明: 网络中有很多主机,我们使用IP地址标识主机的身份。一台主机有多个应用程序,我们使用端口号来标识应用。
端口号范围0~65535,其中0~1024一般给系统使用,或者被知名软件所注册使用,所以为了避免冲突,开发时一般使用1025~65535。当主机某个端口收到消息,只有注册该端口(或者说绑定该端口)的应用才会收到消息。一般一个端口同时只能被一个应用所使用。
补充:占用端口的程序异常退出之后,系统回收端口会有一定时间,此时再次绑定同一个端口会出现,端口被占用的情况。
解决办法:方法1) 换一个端口 方法2)在代码中设置端口复用
//设置端口复用的属性
int on = 1; //1开启 0关闭
int ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if(ret < 0)
{
perror("setsockopt fail\n");
return -1;
}
基本操作步骤:
服务器端:创建tcp监听scoket,绑定地址,accept()接受客户端连接,返回一个与客户端通信的socket。接收数据使用read(),
发送数据用write();使用close()关闭通信。
客户端:创建tcp通信socket,直接向指定的服务器地址发起连接请求,连接成功后,接收数据使用read(),发送数据用write();
使用close()关闭通信。
server端
//1 创建TCP通信socket
int tcp_socket_fd = socket(AF_INET,SOCK_STREAM,0);//AF_INET:标识IPv4协议,IPv6是AF_INET6;SOCK_STREAM标识是TCP协议,UDP是SOCK_DGRAM;0表示默认
//2 绑定IP地址
struct sockaddr_in local_addr = {0};
local_addr.sin_family = AF_INET;//使用IPv4
local_addr.sin_port = htons(port));//port是端口号,网络传输使用大端字节序,使用htons()进行转换。端口是int类型,范围0~65535
local_addr.sin_addr.s_addr = INADDR_ANY; //自动检测本地网卡设备并绑定IP
bind(tcp_socket_fd,(struct sockaddr *)&local_addr,sizeof(local_addr));//绑定socket的IP地址
// 3 设置监听队列(最多可以连接多少个客户端)
listen(tcp_socket_fd,5);
//4 等待,接受客户端的链接
struct sockaddr_in client_addr = {0};//用来存放客户端的地址信息
int len = sizeof(client_addr);//地址信息长度
int new_socket = 0;//与客户端通信的socket
new_socket = accept(tcp_socket_fd,(struct sockaddr *)&client_addr,&len);//阻塞,等待客户端的连接
//5 关闭通信socket
close(new_socket);
close(tcp_socket_fd);
client端
//1 创建TCP通信socket
int tcp_socket_fd = socket(AF_INET,SOCK_STREAM,0);
//2 连接服务器
struct sockaddr_in server_addr = {0};//要连接服务器的地址
server_addr.sin_family = AF_INET;//使用IPv4
server_addr.sin_port = htons(port);//指定要连接服务器的某个端口, 端口是int类型,范围0~65535
server_addr.sin_addr.s_addr = inet_addr(ip);//服务器的ip, ip是字符串类型,要进行相应的转换
connect(tcp_socket_fd ,(struct sockaddr *)&server_addr,sizeof(server_addr));//发起连接服务器请求
//3 关闭通信socket
close(tcp_socket_fd);
功能 | 创建通信端点(可以创建TCP通信端点、UDP通信端点) | |
头文件 | #include <sys/types.h> #include <sys/socket.h> | |
原型 | int socket(int domain, int type, int protocol); | |
参数 | domain | 通信协议簇(包含IPv4,IPv6),详细请看<sys/socket.h> |
type | 通信协议类型(包含TCP,UDP),更多请查询手册: man socket SOCK_STREAM:表示TCP SOCK_DGRAM:表示UDP | |
protocol | 指定与套接字一起使用的特定协议。通常,只有一个协议支持给定协议族中的特定套接字类型,在这种情况下协议可以指定为0.但是,可能存在许多协议,在这种情况下,必须在此指定特定协议方式。 | |
返回值 | 成功:返回一个文件描述符。 失败:返回-1,并设置errno | |
备注 | 详细请查看man手册:man 2 socket |
功能 | 将IP地址信息绑定到socket | |
头文件 | #include <sys/types.h> #include <sys/socket.h> | |
原型 | int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen); | |
参数 | sockfd | 通信socket |
addr | 要绑定的地址信息(包括IP地址,端口号) 通用地址结构体定义: struct sockaddr { 新型的IP地址结构体:(查看新型的结构体信息: gedit /usr/include/linux/in.h ) struct sockaddr_in {
| |
addrlen | 地址信息大小 | |
返回值 | 成功:返回0。 失败:返回-1,并设置errno | |
备注 | 详细请查看man手册:man 2 bind |
功能 | 发起连接请求 | |
头文件 | #include <sys/types.h> #include <sys/socket.h> | |
原型 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); | |
参数 | sockfd | 通信socket |
addr | 要连接的服务器地址 | |
addrlen | 地址信息大小 | |
返回值 | 成功:返回0。 失败:返回-1,并设置errno | |
备注 | 详细请查看man手册:man 2 connect |
功能 | 将无符号短整型数(unsigned short integer)主机字节顺序转换为网络字节序 (h:host主机,to,n:net网络,s:short短整型) | |
头文件 | #include <arpa/inet.h> | |
原型 | uint16_t htons(uint16_t hostshort); | |
参数 | hostshort | 无符号短整型数 |
返回值 | 转换后的值 | |
备注 | 详细请查看man手册:man 3 htons 相关函数: uint32_t htonl(uint32_t hostlong); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort); |
功能 | 将地址cp从IPv4数字和点符号(点分十进制形式)转换为 网络字节顺序的二进制形式。 (cp:char 类型的ip) | |
头文件 | #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> | |
原型 | in_addr_t inet_addr(const char *cp); | |
参数 | cp | ip地址 (如"192.168.21.155") |
返回值 | 成功:转换后的ip值 失败:INADDR_NONE (-1) | |
备注 | 手册中提到: 使用inet_addr()函数是有问题的,因为失败时返回的-1是有效地址(255.255.255.255),应该避免使用。可以使用inet_aton(),inet_pton(3)或getaddrinfo(3),它们提供了一种更清晰的方式来指示错误返回。 详细请查看man手册:man 3 inet_addr 相关函数: int inet_aton(const char *cp, struct in_addr *inp); in_addr_t inet_network(const char *cp); char *inet_ntoa(struct in_addr in); struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host); in_addr_t inet_lnaof(struct in_addr in); in_addr_t inet_netof(struct in_addr in); |
三、例子
例子1:一个客户端,一个服务端,客户端发送信息,服务端接收信息。
效果:
server.c文件
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> //close()
#include <string.h> //strcmp()等字符串操作函数
#include <stdlib.h> //atoi() 字符串转int
int main(int argc, char *argv[])
{
//判断命令行参数是否满足
if(argc != 2)
{
printf("请传递一个端口号\n");
return -1;
}
//将接收端口号并转换为int
int port = atoi(argv[1]);
if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
{
printf("端口号范围应为1025~65535");
return -1;
}
//1 创建tcp通信socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("创建tcp通信socket失败!\n");
return -1;
}
//2 绑定socket地址
struct sockaddr_in server_addr = {0};//存放地址信息
server_addr.sin_family = AF_INET;//AF_INET->IPv4
server_addr.sin_port = htons(port);//端口号
server_addr.sin_addr.s_addr = INADDR_ANY;//让系统检测本地网卡,自动绑定本地IP
int ret = bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) );
if(ret == -1)
{
perror("bind failed!\n");
return -1;
}
//3 设置监听队列,设置为可以接受5个客户端连接
ret = listen(socket_fd, 5);
if(ret == -1)
{
perror("listen falied!\n");
}
printf("server is running!\n");
struct sockaddr_in client_addr = {0};//用来存放客户端的地址信息
int len = sizeof(client_addr);
int new_socket_fd = -1;//存放与客户端的通信socket
//4 等待客户端连接
new_socket_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);
if(new_socket_fd == -1)
{
perror("accpet error!\n");
}
else
{
printf("IP:%s, PORT:%d [connected]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
}
//循环接收信息
while(1)
{
char buf[1024] = {0};
read(new_socket_fd, buf, sizeof(buf));//阻塞,,等待客户端发来消息
printf("receive msg:%s\n", buf);//打印消息
if(strcmp(buf, "exit") == 0)
{
break;//退出循环
}
}
//5 关闭socket
close(new_socket_fd);
close(socket_fd);
return 0;
}
client.c文件
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> //close()
#include <string.h> //strcmp()等字符串操作函数
#include <stdlib.h> //atoi() 字符串转int
int main(int argc, char *argv[])
{
//检查命令行参数是否匹配
if(argc != 3)
{
printf("请传递要连接的服务器的ip和端口号");
return -1;
}
int port = atoi(argv[2]);//从命令行获取端口号
if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
{
printf("端口号范围应为1025~65535");
return -1;
}
//1 创建tcp通信socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("socket failed!\n");
}
//2 连接服务器
struct sockaddr_in server_addr = {0};//服务器的地址信息
server_addr.sin_family = AF_INET;//IPv4协议
server_addr.sin_port = htons(port);//服务器端口号
server_addr.sin_addr.s_addr = inet_addr(argv[1]);//设置服务器IP
int ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(ret == -1)
{
perror("connect failed!\n");
}
else
{
printf("connect server successful!\n");
}
//3 循环发送消息
while(1)
{
char buf[1024] = {0};
printf("Please enter msg:");
scanf("%s",buf);
write(socket_fd,buf,strlen(buf));//发送消息
if(strcmp(buf, "exit") == 0)
{
break;//退出循环
}
}
//4 关闭心贴心socket
close(socket_fd);
return 0;
}
例子2:使用多线程实现 服务器与客户端的双向通信。
效果:
server.c文件
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
#include <pthread.h>
void * recv_msg(void *arg);//接收消息函数声明
int main(int argc, char *argv[])
{
//命令行需要传递一个参数
if(argc != 2)
{
printf("请传递一个端口号\n");
return -1;
}
//从命令行获取端口号
int port = atoi(argv[1]);
if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
{
printf("端口号范围应为1025~65535");
return -1;
}
//1 创建tcp通信socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("socket failed!\n");
return -1;
}
//2 绑定socket地址
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;//AF_INET->IPv4
server_addr.sin_port = htons(port);// server port
server_addr.sin_addr.s_addr = INADDR_ANY;//server ip (auto set by system)
int ret = bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) );
if(ret == -1)
{
perror("bind failed!\n");
return -1;
}
//3 设置监听队列,设置为可以同时连接5个客户端
ret = listen(socket_fd, 5);
if(ret == -1)
{
perror("listen falied!\n");
return -1;
}
printf("server is running!\n");
struct sockaddr_in client_addr = {0};//用来存放客户端的地址信息
int len = sizeof(client_addr);
int new_socket_fd = -1;//存放与客户端的通信socket
//4 等待客户端连接
new_socket_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);
if(new_socket_fd == -1)
{
perror("accpet error!\n");
}
else
{
printf("IP:%s, PORT:%d [connected]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
}
//开启接收线程
pthread_t recv_thread;//存放线程id recv_msg:线程执行的函数,将通信socket:new_socket_fd传递进去
ret = pthread_create(&recv_thread, NULL, recv_msg, (void*)&new_socket_fd);
if(ret != 0)
{
printf("开启线程失败\n");
}
while(1)
{
char buf[1024] = {0};
scanf("%s", buf);
write(new_socket_fd, buf, sizeof(buf));//发送消息
if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)//退出
{
ret = pthread_cancel(recv_thread);//取消线程
break;
}
}
//5 关闭通信socket
close(new_socket_fd);
close(socket_fd);
return 0;
}
//接收线程所要执行的函数 接收消息
void * recv_msg(void *arg)
{
int *socket_fd = (int *)arg;//通信的socket
while(1)
{
char buf[1024] = {0};
read(*socket_fd, buf, sizeof(buf));//阻塞,等待接收消息
printf("receive msg:%s\n", buf);
if(strncmp(buf, "exit", 4) == 0 || strcmp(buf, "") == 0)
{
//通知主线程。。。
break;//退出
}
}
return NULL;
}
client.c文件
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
#include <pthread.h>
void * recv_msg(void *arg);//接收消息函数声明
int main(int argc, char *argv[])
{
//判断参数个数是否匹配
if(argc != 3)
{
printf("请传递要连接的服务器的IP和端口号\n");
return -1;
}
int port = atoi(argv[2]);//从命令行接收参数
if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
{
printf("端口号范围应为1025~65535");
return -1;
}
//1 创建tcp通信socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("socket failed!\n");
}
//2 连接服务器
struct sockaddr_in server_addr = {0};//服务器的地址信息
server_addr.sin_family = AF_INET;//IPv4协议
server_addr.sin_port = htons(port);//服务器端口号
server_addr.sin_addr.s_addr = inet_addr(argv[1]);//设置服务器IP
int ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(ret == -1)
{
perror("connect failed!\n");
}
else
{
printf("connect server successful!\n");
}
//开启接收线程
pthread_t recv_thread;//存放线程id recv_msg:线程执行的函数,将通信socket:new_socket_fd传递进去
ret = pthread_create(&recv_thread, NULL, recv_msg, (void*)&socket_fd);
if(ret != 0)
{
printf("开启线程失败\n");
}
//3 发送消息
while(1)
{
char buf[1024] = {0};//存放要发送的消息
scanf("%s",buf);
write(socket_fd,buf,strlen(buf));
if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
{
ret = pthread_cancel(recv_thread);//取消线程
break;
}
}
//4 关闭通信socket
close(socket_fd);
return 0;
}
//接收线程所要执行的函数 接收消息
void * recv_msg(void *arg)
{
int *socket_fd = (int *)arg;//通信的socket
while(1)
{
char buf[1024] = {0};
read(*socket_fd, buf, sizeof(buf));//阻塞,等待接收消息
printf("receive msg:%s\n", buf);
if(strncmp(buf, "exit", 4) == 0 || strcmp(buf, "") == 0)
{
//通知主线程。。。
break;//退出
}
}
return NULL;
}
例子:3:使用多路复用实现 服务器与客户端的双向通信 。
效果:刚开始的时候,客户端没有连接,是没有超时现象,也就是select()还没有"工作"。
server.c文件
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
int main(int argc, char *argv[])
{
//命令行需要传递一个参数
if(argc != 2)
{
printf("请传递一个端口号\n");
return -1;
}
//从命令行获取端口号
int port = atoi(argv[1]);
if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
{
printf("端口号范围应为1025~65535");
return -1;
}
//1 创建tcp通信socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("socket failed!\n");
return -1;
}
//2 绑定socket地址
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;//AF_INET->IPv4
server_addr.sin_port = htons(port);// server port
server_addr.sin_addr.s_addr = INADDR_ANY;//server ip (auto set by system)
int ret = bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) );
if(ret == -1)
{
perror("bind failed!\n");
return -1;
}
//3 设置监听队列,设置为可以同时连接5个客户端
ret = listen(socket_fd, 5);
if(ret == -1)
{
perror("listen falied!\n");
return -1;
}
printf("server is running!\n");
struct sockaddr_in client_addr = {0};//用来存放客户端的地址信息
int len = sizeof(client_addr);
int new_socket_fd = -1;//存放与客户端的通信socket
//4 等待客户端连接
new_socket_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);
if(new_socket_fd == -1)
{
perror("accpet error!\n");
}
else
{
printf("IP:%s, PORT:%d [connected]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
}
//定义一个文件描述符集合
fd_set fds;
//定义一个时间结构体
struct timeval time;
time.tv_sec = 5;//5秒
time.tv_usec = 0;
//循环监视文件描述符集合
while(1)
{
//清空文件描述符集合
FD_ZERO(&fds);
//把标准输入设备加入到集合中
FD_SET(0,&fds);
//把网络通信文件描述符加入到集合中
FD_SET(new_socket_fd, &fds);
//select会更新超时参数以指示剩余时间
time.tv_sec = 3;//3秒超时
time.tv_usec = 0;
//select会等待time这么久,time可以不设置,即填NULL,那么select会一直阻塞,直到集合中有活跃的描述符
ret = select(new_socket_fd+1,&fds,NULL,NULL,&time);//阻塞等待time,直到集合中有活跃的描述符
if(ret < 0)//错误
{
perror("select fail:");
return -1;
}
else if(ret == 0) //超时
{
printf("timeout\n");
}
else if(ret > 0) //有活跃的 ret为1
{
//printf("ret=%d\n",ret);
//判断是否是 标准输入设备活跃 假设是则发送数据
if(FD_ISSET(0, &fds))//标准输入的描述符是0
{
char buf[1024] = {0};
scanf("%s",buf);
write(new_socket_fd,buf,strlen(buf));
if(strcmp(buf, "exit") == 0)
{
break;
}
}
//判断是否是new_socket_fd活跃,(有消息收到)
if(FD_ISSET(new_socket_fd,&fds))
{
char buf[1024] = {0};
read(new_socket_fd, buf, sizeof(buf));
printf("receive msg:%s\n", buf);
if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
{
break;
}
}
}
printf(".");//用来验证select的阻塞
}
//5 关闭通信socket
close(new_socket_fd);
close(socket_fd);
return 0;
}
client.c文件
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
int main(int argc, char *argv[])
{
//判断参数个数是否匹配
if(argc != 3)
{
printf("请传递要连接的服务器的IP和端口号\n");
return -1;
}
int port = atoi(argv[2]);//从命令行接收参数
if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
{
printf("端口号范围应为1025~65535");
return -1;
}
//1 创建tcp通信socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("socket failed!\n");
}
//2 连接服务器
struct sockaddr_in server_addr = {0};//服务器的地址信息
server_addr.sin_family = AF_INET;//IPv4协议
server_addr.sin_port = htons(port);//服务器端口号
server_addr.sin_addr.s_addr = inet_addr(argv[1]);//设置服务器IP
int ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(ret == -1)
{
perror("connect failed!\n");
}
else
{
printf("connect server successful!\n");
}
//定义一个文件描述符集合
fd_set fds;
//3 发送消息
while(1)
{
//清空文件描述符集合
FD_ZERO(&fds);
//把标准输入设备加入到集合中
FD_SET(0,&fds);
//把网络通信文件描述符加入到集合中
FD_SET(socket_fd,&fds);
ret = select(socket_fd+1,&fds,NULL,NULL,NULL);
if(ret < 0)//错误
{
perror("select fail:");
return -1;
}
else if(ret > 0) //有活跃的
{
//判断是否是 标准输入设备活跃 假设是则发送数据
if(FD_ISSET(0,&fds))
{
char buf[1024] = {0};
scanf("%s",buf);
write(socket_fd,buf,strlen(buf));
if(strcmp(buf, "exit") == 0)
{
break;
}
}
//判断是否是socket_fd活跃,是则说明有数据收到
if(FD_ISSET(socket_fd,&fds))
{
char buf[1024]={0};
read(socket_fd,buf,sizeof(buf));
printf("receive msg:%s\n",buf);
if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
{
break;
}
}
}
printf("·");//用来验证select的阻塞
}
//4 关闭通信socket
close(socket_fd);
return 0;
}
例子4:多个客户端,一个服务器,客户端发送信息,服务端接收消息。(只能接收最后连接的客户端的消息,因为代码中只处理最新获取的socket)
效果:
server.c文件
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
int main(int argc, char *argv[])
{
//判断命令行参数是否满足
if(argc != 2)
{
printf("请传递一个端口号\n");
return -1;
}
//将接收端口号并转换为int
int port = atoi(argv[1]);
if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
{
printf("端口号范围应为1025~65535");
return -1;
}
//1 创建tcp通信socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("创建tcp通信socket失败!\n");
return -1;
}
//2 绑定socket地址
struct sockaddr_in server_addr = {0};//存放地址信息
server_addr.sin_family = AF_INET;//AF_INET->IPv4
server_addr.sin_port = htons(port);//端口号
server_addr.sin_addr.s_addr = INADDR_ANY;//让系统检测本地网卡,自动绑定本地IP
int ret = bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) );
if(ret == -1)
{
perror("bind failed!\n");
return -1;
}
//3 设置监听队列,设置为可以接受5个客户端连接
ret = listen(socket_fd, 5);
if(ret == -1)
{
perror("listen falied!\n");
}
printf("server is running!\n");
struct sockaddr_in client_addr = {0};//用来存放客户端的地址信息
int len = sizeof(client_addr);
int new_socket_fd = -1;//存放与客户端的通信socket
//循环接受客户端
while(1)
{
new_socket_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);//阻塞,直到有客户端连接,才会向下执行
if(new_socket_fd == -1)
{
perror("accpet error!\n");
}
else
{
printf("IP:%s, PORT:%d [connected]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
}
char buf[1024] = {0};
read(new_socket_fd, buf, sizeof(buf));//接收最新客户端的消息。
printf("receive msg:%s\n", buf);
if(strcmp(buf, "exit") == 0)
{
break;
}
}
//5 关闭通信socket
close(new_socket_fd);
close(socket_fd);
return 0;
}
clcient.c文件
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h> //close()
#include <string.h> //strcmp()等字符串操作函数
#include <stdlib.h> //atoi() 字符串转int
int main(int argc, char *argv[])
{
//检查命令行参数是否匹配
if(argc != 3)
{
printf("请传递要连接的服务器的ip和端口号");
return -1;
}
int port = atoi(argv[2]);//从命令行获取端口号
if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
{
printf("端口号范围应为1025~65535");
return -1;
}
//1 创建tcp通信socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("socket failed!\n");
}
//2 连接服务器
struct sockaddr_in server_addr = {0};//服务器的地址信息
server_addr.sin_family = AF_INET;//IPv4协议
server_addr.sin_port = htons(port);//服务器端口号
server_addr.sin_addr.s_addr = inet_addr(argv[1]);//设置服务器IP
int ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(ret == -1)
{
perror("connect failed!\n");
}
else
{
printf("connect server successful!\n");
}
//3 循环发送消息
while(1)
{
char buf[1024] = {0};
printf("Please enter msg:");
scanf("%s",buf);
write(socket_fd,buf,strlen(buf));//发送消息
if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
{
break;//退出循环
}
}
//4 关闭心贴心socket
close(socket_fd);
return 0;
}
例子5:使用多线程实现 服务器与多个客户端的双向通信。
(没有检查数组索引越界,在线程退出时没有处理好资源释放)
效果:
sever.c文件
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
#include <pthread.h>
void * recv_msg(void *arg);//接收消息函数声明
void * send_msg(void *arg);//发送消息函数声明
int main(int argc, char *argv[])
{
//命令行需要传递一个参数
if(argc != 2)
{
printf("请传递一个端口号\n");
return -1;
}
//从命令行获取端口号
int port = atoi(argv[1]);
if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
{
printf("端口号范围应为1025~65535");
return -1;
}
//1 创建tcp通信socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("socket failed!\n");
return -1;
}
//2 绑定socket地址
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;//AF_INET->IPv4
server_addr.sin_port = htons(port);// server port
server_addr.sin_addr.s_addr = INADDR_ANY;//server ip (auto set by system)
int ret = bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) );
if(ret == -1)
{
perror("bind failed!\n");
return -1;
}
//3 设置监听队列,设置为可以同时连接5个客户端
ret = listen(socket_fd, 5);
if(ret == -1)
{
perror("listen falied!\n");
return -1;
}
printf("server is running!\n");
struct sockaddr_in client_addr = {0};//用来存放客户端的地址信息
int len = sizeof(client_addr);
int i = 0;
int socket_arr[5] = {0};//存放与客户端的通信socket
pthread_t send_thread = 0;//存放发送线程的id
while(1)//不断的接受客户端的请求
{
int new_socket_fd = -1;
new_socket_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);
if(new_socket_fd == -1)
{
perror("accpet error!\n");
}
else
{
printf("[ID:%d] IP:%s, PORT:%d [connected]\n", i, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
socket_arr[i] = new_socket_fd;
//开启接收线程 一条接收线程对应一个连接的客户端
pthread_t recv_thread;
pthread_create(&recv_thread, NULL, recv_msg, (void*)&socket_arr[i]);
i++;
if(send_thread == 0)//只在第一次创建发送线程
{
pthread_create(&send_thread, NULL, send_msg, (void*)socket_arr);
}
}
}
//5 关闭socket
close(socket_fd);
return 0;
}
//接收线程所要执行的函数 接收消息
void * recv_msg(void *arg)
{
int *socket_fd = (int *)arg;//通信的socket
while(1)
{
char buf[1024] = {0};
read(*socket_fd, buf, sizeof(buf));//阻塞,等待接收消息
printf("receive msg:%s\n", buf);
if(strncmp(buf, "exit", 4) == 0 || strcmp(buf, "") == 0)
{
//通知主线程。。。
break;//退出
}
}
return NULL;
}
//发送线程所要执行的函数 发送消息
void * send_msg(void *arg)
{
int (*socket_arr_pt)[5] = (int (*) [5])arg;//数组指针
while(1)
{
int id = 0;
char buf[1024] = {0};
scanf("%d %s", &id, buf);
if(id<0 || id>4)
{
printf("id 0~4");
continue;
}
write( (*socket_arr_pt)[id], buf, sizeof(buf));
if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
{
break;
}
}
//close(socket_arr[0]);
return NULL;
}
clinet.c文件
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
#include <pthread.h>
void * recv_msg(void *arg);
int main(int argc, char *argv[])
{
//判断参数个数是否匹配
if(argc != 3)
{
printf("请传递要连接的服务器的IP和端口号\n");
return -1;
}
int port = atoi(argv[2]);//从命令行接收参数
if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
{
printf("端口号范围应为1025~65535");
return -1;
}
//1 创建tcp通信socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("socket failed!\n");
}
//2 连接服务器
struct sockaddr_in server_addr = {0};//服务器的地址信息
server_addr.sin_family = AF_INET;//IPv4协议
server_addr.sin_port = htons(port);//服务器端口号
server_addr.sin_addr.s_addr = inet_addr(argv[1]);//设置服务器IP
int ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(ret == -1)
{
perror("connect failed!\n");
}
else
{
printf("connect server successful!\n");
}
//开启接收线程
pthread_t recv_thread;//存放线程id recv_msg:线程执行的函数,将通信socket:new_socket_fd传递进去
ret = pthread_create(&recv_thread, NULL, recv_msg, (void*)&socket_fd);
if(ret != 0)
{
printf("开启线程失败\n");
}
//3 循环发送消息
while(1)
{
char buf[1024] = {0};
scanf("%s",buf);
write(socket_fd,buf,strlen(buf));
if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
{
pthread_cancel(recv_thread);//取消接收线程
break;
}
}
//4 关闭socket
close(socket_fd);
return 0;
}
//接收线程所要执行的函数 接收消息
void * recv_msg(void *arg)
{
int *socket_fd = (int *)arg;//通信的socket
while(1)
{
char buf[1024] = {0};
read(*socket_fd, buf, sizeof(buf));//阻塞,等待接收消息
printf("receive msg:%s\n", buf);
if(strncmp(buf, "exit", 4) == 0 || strcmp(buf, "") == 0)
{
//通知主线程。。。
break;//退出
}
}
return NULL;
}
例子6:使用多路复用实现 服务器与多个客户端的双向通信。
效果:
server.c文件
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
int get_max_socket_fd(int socket_arr[]);//获取最大的通信socket描述符。
int main(int argc, char *argv[])
{
//判断命令行参数是否满足
if(argc != 2)
{
printf("请传递一个端口号\n");
return -1;
}
//将接收端口号并转换为int
int port = atoi(argv[1]);
if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
{
printf("端口号范围应为1025~65535");
return -1;
}
//1 创建tcp通信socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("创建tcp通信socket失败!\n");
return -1;
}
//2 绑定socket地址
struct sockaddr_in server_addr = {0};//存放地址信息
server_addr.sin_family = AF_INET;//AF_INET->IPv4
server_addr.sin_port = htons(port);//端口号
server_addr.sin_addr.s_addr = INADDR_ANY;//让系统检测本地网卡,自动绑定本地IP
int ret = bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) );
if(ret == -1)
{
perror("bind failed!\n");
return -1;
}
//3 设置监听队列,设置为可以接受5个客户端连接
ret = listen(socket_fd, 5);
if(ret == -1)
{
perror("listen falied!\n");
}
printf("server is running!\n");
struct sockaddr_in client_addr = {0};//用来保存客户端的地址信息
int len = sizeof(client_addr);
int socket_fd_arr[5] = {-1, -1, -1, -1, -1};//用来保存5个客户端的通信socket
//定义一个文件描述符集合
fd_set fds;
//定义一个时间结构体
struct timeval time;
time.tv_sec = 3;//超时时间
time.tv_usec = 0;
//循环监视文件描述符集合
int new_socket_fd = -1;
int max_socket_fd = -1;//保存最大的socket描述符
int index = -1, i;
//循环监视文件描述符集合
while(1)
{
//清空文件描述符集合
FD_ZERO(&fds);
//把标准输入设备加入到集合中
FD_SET(0, &fds);
FD_SET(socket_fd, &fds);//将监听socket添加到集合中
//把网络通信文件描述符加入到集合中
for(i=0; i<=index; i++)
{
FD_SET(socket_fd_arr[i],&fds);
}
//获取最大的通信socket描述符。
max_socket_fd = get_max_socket_fd(socket_fd_arr);
if(max_socket_fd == -1)
{
max_socket_fd = socket_fd;
}
ret = select(max_socket_fd+1,&fds,NULL,NULL,&time);//检查集合中是否有活跃的描述符
if(ret < 0)//错误
{
perror("select fail");
return -1;
}
else if(ret == 0) //超时
{
//printf("timeout1\n");
}
else if(ret > 0) //有活跃的
{
if(FD_ISSET(socket_fd,&fds))//监听socket活跃,说明有客户端请求连接
{
new_socket_fd = accept( socket_fd, (struct sockaddr *)&client_addr, &len);
if(new_socket_fd == -1)
{
perror("accpet error!\n");
continue;
}
else
{
if(index>=5)
{
index = 4;
printf("index>=5\n");
continue;
}
socket_fd_arr[++index] = new_socket_fd;//将通信socket保存到数组中
printf("[ID:%d] IP:%s, PORT:%d [connected]\n", index, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
}
}
//判断是否 标准输入设备活跃 假设是则发送数据
if(FD_ISSET(0,&fds))
{
char buf[1024] = {0};
int client = -1;
scanf("%d %s", &client, buf);
if(client>=0 && client<=index)
{
write(socket_fd_arr[client],buf,strlen(buf));
}
else //给所有的客户端发消息
{
for(i=0; i<=index; i++)
{
write(socket_fd_arr[i],buf,strlen(buf));
}
}
if(strcmp(buf, "exit") == 0)
{
break;
}
}
//判断是否有收到消息
for(i=0; i<=index; i++)
{
if(FD_ISSET(socket_fd_arr[i],&fds))//判断通信socket是否有活跃
{
char buf[1024] = {0};
read(socket_fd_arr[i], buf, sizeof(buf));
if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
{
break;
}
else if(strlen(buf)>0)
{
printf("receive msg form [ID:%d]:%s\n", i, buf);
}
}
}
}
}
//5 关闭通信socket
close(new_socket_fd);
close(socket_fd);
return 0;
}
//获取最大的通信socket描述符。
int get_max_socket_fd(int socket_arr[])
{
int max = -1;
int i;
for(i=0; i<5; i++)
{
if(socket_arr[i]>max)
{
max = socket_arr[i];
}
}
return max;
}
client.c文件
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h> //atoi()
int main(int argc, char *argv[])
{
//检查命令行参数是否匹配
if(argc != 3)
{
printf("请传递要连接的服务器的ip和端口号");
return -1;
}
int port = atoi(argv[2]);//从命令行获取端口号
if( port<1025 || port>65535 )//0~1024一般给系统使用,一共可以分配到65535
{
printf("端口号范围应为1025~65535");
return -1;
}
//1 创建tcp通信socket
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd == -1)
{
perror("socket failed!\n");
}
//2 连接服务器
struct sockaddr_in server_addr = {0};//服务器的地址信息
server_addr.sin_family = AF_INET;//IPv4协议
server_addr.sin_port = htons(port);//服务器端口号
server_addr.sin_addr.s_addr = inet_addr(argv[1]);//设置服务器IP
int ret = connect(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(ret == -1)
{
perror("connect failed!\n");
}
else
{
printf("connect server successful!\n");
}
//要监视的描述符集合
fd_set fds;
//3 send msg
while(1)
{
//清空文件描述符集合
FD_ZERO(&fds);
//把标准输入设备加入到集合中
FD_SET(0,&fds);
//把网络通信文件描述符加入到集合中
FD_SET(socket_fd,&fds);
ret = select(socket_fd+1,&fds,NULL,NULL,NULL);
if(ret < 0)//错误
{
perror("select fail:");
return -1;
}
else if(ret > 0) //有活跃的
{
//判断是否 标准输入设备活跃 假设是则发送数据
if(FD_ISSET(0,&fds))
{
char buf[1024] = {0};
scanf("%s",buf);
write(socket_fd,buf,strlen(buf));
if(strcmp(buf, "exit") == 0)
{
break;
}
}
//判断是否有收到数据
if(FD_ISSET(socket_fd,&fds))
{
char buf[1024]={0};
read(socket_fd,buf,sizeof(buf));
printf("receive msg:%s\n",buf);
if(strcmp(buf, "exit") == 0 || strcmp(buf, "") == 0)
{
break;
}
}
}
}
//4 关闭通信socket
close(socket_fd);
return 0;
}
----------------------------------------------------多路复用-----------------------------
再某些情况下代替多线程,以便节省资源。
功能 | 监视多个文件描述符,阻塞等待设定的时间,直到一个或多个文件描述符变为“准备好”,继续往下执行。 | |
头文件 | #include <sys/time.h> #include <sys/types.h> #include <unistd.h> | |
原型 | int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); | |
参数 | nfds | 要监视的描述符集合中数值最大的描述符 |
readfds | 读文件描述符集合,通常设置为要监视的描述符集合 | |
writefds | 写文件描述符集合,通常设置为NULL | |
exceptfds | 其它文件描述符集合,通常设置为NULL | |
timeout | 超时时间(阻塞等待的时间,时间一到,就会往下执行。如果为NULL,会一直等到有描述符活跃为止) 时间结构体 (定义在<sys/time.h> 头文件中) struct timeval { | |
返回值 | -1:出错 0:超时 1:有活跃的文件描述符 | |
备注 | 详细请查看man手册:man 2 select 相关函数: void FD_CLR(int fd, fd_set *set); //将某个描述符 从集合中清除出去 注:select会更新 超时时间timeout,所以如果多次使用select需要重置timeout。 往下执行时,将修改每个文件描述符集以指示哪些文件描述符实际更改了状态。 因此,如果在循环中使用select(),则需要在每次调用之前对集合进行重新定义。 |
更多推荐
所有评论(0)