1 说明
本篇是两个程序,分为客户端和服务端程序,作为工作中测试的一个通信程序例程。
2 客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#define SERVER_ADDR "192.168.25.15"
#define SERVER_PORT 4819
#define SOCKET_BUF_LEN 1024
#define Tranverse32(X) ((((unsigned int)(X) & 0xff000000) >> 24) | \
(((unsigned int)(X) & 0x00ff0000) >> 8) |\
(((unsigned int)(X) & 0x0000ff00) << 8) |\
(((unsigned int)(X) & 0x000000ff) << 24))
int main()
{
int fd = 0;
int i = 0;
struct sockaddr_in serveraddr;
int socket_opt_val = 0;
unsigned char buf[SOCKET_BUF_LEN] = {'\0'};
unsigned char rcv_buf[SOCKET_BUF_LEN] = {0};
unsigned int i_value = 0;
int len = 0;
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Cread Sock Error, Return");
return -1;
}
printf("[%d]Cread Sock Ok\n", fd);
//设置该套接字接口可以与已经在使用的地址绑定(BF548有多个套接字)
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, \
&socket_opt_val, sizeof(socket_opt_val)) < 0) {
perror("Setsockopt Error, Return");
return -1;
}
printf("Setsockopt Ok\n");
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERVER_PORT);
serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
if (connect(fd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr_in)) < 0) {
perror("Connect Error, Return");
return -1;
}
printf("Connect Ok\n");
buf[0] = 0x06;
buf[1] = 0x01;
buf[2] = 0x00;
buf[3] = 0x00;
if (send(fd, buf, 4, 0) < 0) {
perror("Send Error, Close This Channel, Return");
close(fd);
return -1;
}
printf("Send Ok\n");
while (1) {
memset(buf, 0, SOCKET_BUF_LEN);
if ((len = recv(fd, rcv_buf, SOCKET_BUF_LEN, 0)) < 0) {
perror("Recv Error, Break");
break;
} else {
rcv_buf[len] = '\0';
if((len % 4 == 0) && (len != 0 ))
{
for(i = 0 ; i < len ; i = i + 4)
{
// printf("0x%0x,0x%0x,0x%0x,0x%0x\n",rcv_buf[i+0],rcv_buf[i+1],rcv_buf[i+2],rcv_buf[i+3]);
//i_value = (rcv_buf[i] << 24) | (rcv_buf[i+1] << 16) | ((rcv_buf[i+2] << 8)) | ((rcv_buf[i+3] << 0));
i_value = (rcv_buf[i]) | (rcv_buf[i+1] << 8) | ((rcv_buf[i+2] << 16)) | ((rcv_buf[i+3] << 24));
printf("0x%08x\n",i_value);
//if(i_value == 0xc612c)
// printf("---recv delay----------\n");
//if(i_value == 0x2135)
// printf("------first channel------\n");
i_value = Tranverse32(i_value);
if((i_value >> (32-6)) == 15)
{
printf("-----row:%0x,col:%d,val:%d-----\n",(i_value>>21)&0x1f,(i_value>>16)&0x1f,i_value&0xff);
}
}
}
}
}
printf("Quit\n");
close(fd);
return 0;
}
3 服务端
3.1 简单的服务端程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#define SERVER_ADDR "192.168.25.15"
#define SERVER_PORT 4819
#define SOCKET_BUF_LEN 1024
int main(int argc,char *argv[])
{
int server_fd = 0;
int i = 0;
struct sockaddr_in serveraddr;
int socket_opt_val = 0;
unsigned char buf[SOCKET_BUF_LEN] = {'\0'};
unsigned char rcv_buf[SOCKET_BUF_LEN] = {0};
unsigned int i_value = 0;
int len = 0;
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Cread Sock Error, Return");
return -1;
}
printf("[%d]Cread Sock Ok\n", server_fd);
//ctrl+c prevent bind already
int mw_optval = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&mw_optval,sizeof(mw_optval));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERVER_PORT);
serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
if(bind(server_fd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
printf("----bind server error----\r\n");
close(server_fd);
return -1;
}
//listen the socket
int listen_sock = listen(server_fd, 5);
if(listen_sock < 0) {
printf("---listen server socket error---\r\n");
close(server_fd);
return -1;
}
struct sockaddr_in peer;
socklen_t peer_len = sizeof(struct sockaddr_in);
int accept_fd = accept(server_fd, (struct sockaddr*)&peer, &peer_len);
if(accept_fd < 0)
{
printf("---accept client error:%d----\r\n",accept_fd);
close(server_fd);
return -1;
}
else
{
printf("connected with ip: %s and port: %d\n", inet_ntop(AF_INET,&peer.sin_addr, buf, 1024), ntohs(peer.sin_port));
}
while(1) {
memset(buf, '\0', sizeof(buf));
ssize_t size = read(accept_fd, buf, sizeof(buf) - 1);
if(size > 0)
{
printf("client#: %s\n", buf);
}
else if(size == 0)
{
printf("read is done...\n");
break;
}
else
{
printf("----read error----\r\n");
break;
}
printf("server please enter: ");
fflush(stdout);
size = read(0, buf, sizeof(buf) - 1);
if(size > 0)
{
buf[size - 1] = '\0';
printf("console:read:%s\r\n",buf);
}
else if(size == 0)
{
printf("read is done...\n");
break;
}
else
{
perror("read");
break;
}
write(accept_fd, buf, strlen(buf));
}
close(server_fd);
return 0;
}
服务端的测试,可以使用sokit工具来连接,做简单的消息交互。
sokit工具如下所示:
3.2 使用select优化服务端程序
有这样的需求,服务器只需要接受一个客户端即可,但是,有这么一个情况:在开始的时候,客户端和服务器是连在一起的,但是,途中将网线拔掉了,接着客户端也关闭了。此时再连接网线,再次开启客户端,就会发现一般情况下,会发生连不上服务器的情况。
这种情况发生的原因主要在,第二次连接的时候,服务器的第一次连接还没有关闭。
问题点就在两次连接,产生了两个客户端。上边的程序是属于单客户端单服务器的情况。
所以,我们需要解决的问题点,那就变成了,在第二次连接的时候,服务端只需要关闭第一个客户端即可。
这里就要谈到在linux应用层上的select函数的使用。
select函数原型如下:
int select (int maxfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
解释:
select系统调用是用来监测多个文件句柄的状态变化的。错误返回-1。
最后一个参数,表明的是需要等待的时间:
struct timeval{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
}
若timeout=NULL的时候,表示等待无限长时间。可以被一个信号中断。
若tv_sec=0并且tv_usec=0,表示不等待。加入的描述符集都会被测试的。
若tv_sec!=0或者tv_usec!=0,表示等待指定的时间。
fd_set类型变量的每一位表示的是一个描述符。我摘抄一张图:
控制fd_set变量,可以使用如下几个宏:
#include <sys/select.h>
int FD_ZERO(int fd, fd_set *fdset);
int FD_CLR(int fd, fd_set *fdset);
int FD_SET(int fd, fd_set *fd_set);
int FD_ISSET(int fd, fd_set *fdset);
如何使用这几个宏?
首先需要将fd_set变量设置为0,也就需要使用FD_ZERO,然后可以设置我们需要关心的描述符,可以使用FD_SET,相对应的就要使用FD_CLR,最后是select函数返回,可以使用FD_ISSET来判断所关心的fd_set变量是否有变化。
关于这些内容,我们可以直接参考链接: 链接
到这里,可以直接贴上一部分简单的程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#define SERVER_ADDR "192.168.25.15"
#define SERVER_PORT 4819
#define SOCKET_BUF_LEN 1024
unsigned char recv_data[SOCKET_BUF_LEN];
typedef struct _CLIENT{
int fd;
struct sockaddr_in addr;
} CLIENT;
int main(int argc,char *argv[])
{
int server_fd = 0;
int i = 0;
struct sockaddr_in serveraddr;
int socket_opt_val = 0;
unsigned char buf[SOCKET_BUF_LEN] = {'\0'};
unsigned char rcv_buf[SOCKET_BUF_LEN] = {0};
unsigned int i_value = 0,sockfd,bytes_received;
int len = 0;
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Cread Sock Error, Return");
return -1;
}
printf("[%d]Cread Sock Ok\n", server_fd);
//ctrl+c prevent bind already
int mw_optval = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&mw_optval,sizeof(mw_optval));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERVER_PORT);
serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
if(bind(server_fd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
printf("----bind server error----\r\n");
close(server_fd);
return -1;
}
//listen the socket
int listen_sock = listen(server_fd, 5);
if(listen_sock < 0) {
printf("---listen server socket error---\r\n");
close(server_fd);
return -1;
}
struct sockaddr_in peer;
socklen_t peer_len = sizeof(struct sockaddr_in);
int count = 0;
fd_set server_fd_set;
int max_fd = -1;
while(1){
FD_ZERO(&server_fd_set);
FD_SET(server_fd, &server_fd_set);
max_fd = server_fd;
CLIENT client[5];
int nready;
for (i = 0; i < 5; i++) {
client[i].fd = -1;
}
while(1){
nready = select(max_fd+1, &server_fd_set, NULL, NULL, NULL);
/* select the server fd to check if a new connection is avaliable...*/
if (FD_ISSET(server_fd, &server_fd_set)) {
int accept_fd = accept(server_fd, (struct sockaddr*)&peer, &peer_len);
if(accept_fd == -1){
printf("---accept error----\r\n");
continue;
}
else
{
printf("connected with ip: %s and port: %d\n", inet_ntop(AF_INET,&peer.sin_addr, buf, 1024), ntohs(peer.sin_port));
}
count ++;
}
}
}
close(server_fd);
return 0;
}
一些说明
在这个程序里,测试的时候,可以先运行程序,然后在一切正常的情况下,使用sokit连接服务器,然后拔掉网线,断开客户端tcp连接,再接上网线,连接客户端,然后拔掉网线,断掉客户端tcp连接。然后再连接网线,开启客户端。。。测试结果如下:
所以,我们可以得到一个结论,使用select可以监测到server_fd的变化的。在此基础上,我们在检测到第二个连接的时候,断开第一个连接即可。
同样,也是直接贴上代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#define SERVER_ADDR "192.168.1.16"
#define SERVER_PORT 4819
#define SOCKET_BUF_LEN 1024
unsigned char recv_data[SOCKET_BUF_LEN];
typedef struct _CLIENT{
int fd;
struct sockaddr_in addr;
} CLIENT;
int main(int argc,char *argv[])
{
int server_fd = 0;
int i = 0;
struct sockaddr_in serveraddr;
int socket_opt_val = 0;
unsigned char buf[SOCKET_BUF_LEN] = {'\0'};
unsigned char rcv_buf[SOCKET_BUF_LEN] = {0};
unsigned int i_value = 0,sockfd,bytes_received;
int len = 0;
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Cread Sock Error, Return");
return -1;
}
printf("[%d]Cread Sock Ok\n", server_fd);
//ctrl+c prevent bind already
int mw_optval = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&mw_optval,sizeof(mw_optval));
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERVER_PORT);
serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDR);
if(bind(server_fd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) < 0) {
printf("----bind server error----\r\n");
close(server_fd);
return -1;
}
//listen the socket
int listen_sock = listen(server_fd, 5);
if(listen_sock < 0) {
printf("---listen server socket error---\r\n");
close(server_fd);
return -1;
}
struct sockaddr_in peer;
socklen_t peer_len = sizeof(struct sockaddr_in);
int count = 0;
fd_set server_fd_set;
int max_fd = -1;
while(1){
CLIENT client;
int nready;
client.fd = -1;
while(1){
FD_ZERO(&server_fd_set);
FD_SET(server_fd, &server_fd_set);
max_fd = server_fd;
if(server_fd > max_fd)
max_fd = server_fd;
if(client.fd!=-1){
FD_SET(client.fd, &server_fd_set);
if(client.fd > max_fd)
max_fd = client.fd;
}
nready = select(max_fd+1, &server_fd_set, NULL, NULL, NULL);
/* select the server fd to check if a new connection is avaliable...*/
if (FD_ISSET(server_fd, &server_fd_set)) {
int accept_fd = accept(server_fd, (struct sockaddr*)&peer, &peer_len);
if(accept_fd == -1){
printf("---accept error----\r\n");
continue;
}
else
{
printf("port: %d\n", ntohs(peer.sin_port));
//printf("connected with ip: %s and port: %d\n", inet_ntop(AF_INET,&peer.sin_addr, buf, 1024), ntohs(peer.sin_port));
}
count ++;
/*if client exists,clear the last client fd*/
if(client.fd != -1){
close(client.fd);
client.fd = -1;
}
/* add a fd to client*/
client.fd = accept_fd;
}
if(client.fd != -1){
/* poll the client status*/
if (FD_ISSET(client.fd, &server_fd_set))
{
int byte_num = recv(client.fd,rcv_buf,SOCKET_BUF_LEN,0);
if(byte_num>0){
if(byte_num>SOCKET_BUF_LEN){
byte_num=SOCKET_BUF_LEN;
}
rcv_buf[byte_num]='\0';
printf("client:%s\n",rcv_buf);
}
else if(byte_num < 0)
{
printf("client error.\n");
}
else
{
FD_CLR(client.fd,&server_fd_set);
close(client.fd);
client.fd=-1;
printf("client quit\n");
}
}
}
}
}
close(server_fd);
return 0;
}
实现的效果如下图所示:
我做一个基本的简单的讲解:
1.在第二个while(1)中,先清除所有检测的值,这是因为,在第二个客户端连接的时候,第一个客户端就关闭了,这个时候检测的set集合就改变了,我们能清除第一个客户端在集合中的值,但是并没有办法改变max的值,所以清除所有的要检测的值,然后重新设置值,是一个办法。
2.select检测到服务端有变化,就知道有客户端连接上了,所以获取到accept_fd,然后才能置位set集合,也就能检测客户端是否接收数据了。
3.客户端接收数据,处理数据。
一点注意:
我前面的测试过程,都是在树莓派A20上进行的,在使用inet_ntop的时候,并没有出错。但是在使用64位主机的时候,最好还是加上头文件:
#include <arpa/inet.h>
错误信息如下所示:
加上头文件后,发送数据: