简介
使用TCP协议,实现开发板与电脑主机之间的网络通信。
一、TCP概念
传输控制协议 TCP(Transmission Control Protocol):面向连接的,数据传输的单位是报文段,能够提供可靠的交付,是传输层(负责向两个主机进程之间的通信提供服务)的协议之一。
TCP 向它的应用程序提供了面向连接的服务。这种服务有 2 个特点:可靠传输、流量控制(即发送方/接收方速率匹配)。它包括了应用层报文划分为短报文,并提供拥塞控制机制。
二、TCP网络通信交互流程
服务器端 | 客户端 |
(1)socket:创建一个套接字(套接字:对网络中不同主机上的应用进程之间进行双向通信的端点的抽象,一个套接字就是网络上进程通信的一端) | |
(2)bind:对套接字进行地址和端口的绑定。 | (1)socket:创建一个套接字 |
(3)listen:监听、等待客户端连接 | (2)connect:连接服务器 |
(4)accept:获得连接请求,并且建立连接 | |
(5)send/receive:发送或接收数据 | (3)send/receive:发送或接收数据 |
(6)close:关闭连接 | (4)close:关闭连接 |
三、网络编程主要函数
1、socket函数
创建一个套接字。成功返回 文件描述符,失败返回-1
int socket(int domain, int type,int protocol);
domain是网络程序所在的主机采用的通讯协族(AF_UNIX 和 AF_INET 等),一般使用AF_INET,因为AF_INET是针对Internet的,允许远程通信。
type 是网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM 等)。SOCK_STREAM 表明用的是 TCP 协议; SOCK_DGRAM 表明用的是 UDP 协议。这里使用SOCK_STREAM。
protocol由于指定了 type,所以这里一般只要用 0 来代替。
2、bind函数
将地址和端口绑定到一个套接字。成功返回 0,失败返回-1
int bind(int sockfd, struct sockaddr_in *my_addr, int addrlen);
//sockaddr_in 的定义:
struct sockaddr_in{
unsigned short sin_family; //如果使用Internet,一般为 AF_INET
unsigned short sin_port; //监听的端口号,要建立连接的服务器和客户端的端口必须一致
struct in_addr sin_addr; //设置为 INADDR_ANY 表示可以和任何的主机通信。
unsigned char sin_zero[8];
}
sockfd 是由 socket 函数调用返回的文件描述符。
my_addr 是一个指向 sockaddr 的指针。
addrlen 是 sockaddr 结构的长度。
3、 listen 函数
表明服务器可以接受连接请求。成功返回 0,失败返回-1。
int listen(int sockfd,int backlog);
sockfd 是 bind 后的文件描述符。
backlog 设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
listen 函数将 bind 的文件描述符变为监听套接字。
4、accept 函数
服务器使用此函数可以获得连接请求,并且建立连接。成功返回 服务器端的文件描述符,失败返回-1。
int accept(int sockfd, struct sockaddr *addr,int *addrlen);
sockfd 是 listen 后的文件描述符。
addr,addrlen 是用来给客户端的程序填写的,服务器端只要传递指针就可以了, bind,listen 和 accept 是服务器端用的函数。
accept 调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。
5、connect 函数
可以用 connect 建立一个连接,在 connect 中所指定的地址是想与之通信的服务器的地址。成功时返回 0,失败时返回-1。
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);
sockfd 是 socket 函数返回的文件描述符。
serv_addr 储存了服务器端的连接信息,其中 sin_add 是服务端的地址。
addrlen 是 serv_addr 的长度。
connect 函数是客户端用来同服务端连接的。
6、send 函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd 指定发送端套接字描述符;
buf 指明一个存放应用程序要发送数据的缓冲区;
len 指明实际要发送的数据的字节数;
flags 一般置 0。
客户或者服务器应用程序都用 send 函数来向 TCP 连接的另一端发送数据。
7、 recv 函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd 指定接收端套接字描述符;
buf 指明一个缓冲区,该缓冲区用来存放 recv 函数接收到的数据;
len 指明 buf 的长度;
flags 一般置 0。
客户或者服务器应用程序都用 recv 函数从 TCP 连接的另一端接收数据。
四、编写程序
1.服务器端(server.c)
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#define SERVER_PORT 8888 //端口号
#define BACKLOG 10 //最大允许连接客户端数
int main(int argc,char **argv)
{
int iSocketServer; // 接收服务器文件描述符
int iSocketClient; // 接收客户端文件描述符
struct sockaddr_in tSocketServerAddr; // 主机网络信息结构体
struct sockaddr_in tSocketClientAddr; // 客户端网络信息结构体
int iRet; // 接收返回值
int iAddrLen; // 结构体地址长度
int iRecvLen; // 接收数据长度
int iSendLen; // 发送数据长度
int iClientNum = -1; // 客户端数量
unsigned char ucRecvBuf[1000]; // 接收数组
unsigned char ucSendBuf[1000]; // 发送数组
iSocketServer = socket(AF_INET, SOCK_STREAM, 0); // 建立服务器的套接字文件描述符
if(-1 == iSocketServer) // 如果接收到失败
{
printf("Socket error\n");
return -1;
}
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); //把主机的字节序转换成网络字节序
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero,0,8);
// 对套接字进行地址和端口的绑定
iRet = bind(iSocketServer, (struct sockaddr *)&tSocketServerAddr,sizeof(struct sockaddr));
if(iRet == -1) // 如果接收到失败
{
printf("bind error!\n");
return -1;
}
// 监听服务器,并且允许连接的客户端数量为10
iRet = listen(iSocketServer, BACKLOG);
if(iRet == -1)
{
printf("listen error!\n");
return -1;
}
while(1)
{
iAddrLen = sizeof(struct sockaddr);
iSocketClient = accept(iSocketServer,(struct sockaddr *)&tSocketClientAddr,&iAddrLen);
if(iSocketClient != -1)
{
printf("connect success!\n");
iClientNum++;
printf("get connect from client %d\n",iClientNum);
if(!fork()) //复制一个子进程
{
/* 子进程源码 */
while(1)
{
/* 接收客户端发来的数据并显示出来 */
iRecvLen = recv(iSocketClient,ucRecvBuf,999,0);
if(iRecvLen <= 0)
{
close(iSocketClient);
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0';
printf("get mag from client %d: %s\n",iClientNum,ucRecvBuf);
}
}
}
}
}
close(iSocketServer);
return 0;
}
2.客户端(client.c)
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
/*
* socket
* connect
* send/rcv
*/
#define SERVER_PORT 8888
int main(int argc,char **argv)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iRet;
int iSenLen;
int iRecvLen; // 接收数据长度
unsigned char ucRecvBuf[1000]; // 接收数组
unsigned char ucSendBuf[1000];
if(argc != 2)
{
printf("usage:\n");
printf("%s <server_ip>\n",argv[0]);
return -1;
}
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
if(iSocketClient == -1)
{
printf("socket error!\n");
return -1;
}
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); //把主机的字节序转换成网络字节序
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
if(0 == inet_aton(argv[1],&tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n");
return -1;
}
memset(tSocketServerAddr.sin_zero,0,8);
iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr,sizeof(struct sockaddr));
if(iRet == -1)
{
printf("connect error!\n");
return -1;
}
while(1)
{
if(fgets(ucSendBuf, 999, stdin));
{
printf("send msg success! msg:%s",ucSendBuf);
iSenLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if(iSenLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}
return 0;
}
五、编译测试
1、先在虚拟机编译测试一下
使用以下代码进行编译:
gcc -o 程序名 程序.c
编译完成之后,再新建一个窗口,一个用来运行server,一个用来运行client。
可以看到客户端发送信息,服务器接收到了数据。
2、确保虚拟机和开发板的网络连接在同一网络下。
3、接下来我们把客户端(client)程序交叉编译一下,下载到开发板执行(这里如果不知道怎么下载,可以看一下我的上一篇文章)
arm-linux-gnueabi-gcc -o client client.c //这里交叉编译工具根据自己情况修改
在开发板上先执行以下命令,给予程序可读、可写、可执行的权限。
chmod 777 client
然后在电脑主机运行server程序,之后再在开发板上执行client程序,就可以实现电脑主机和开发板之间的网络通信。
主机执行:./server
客户端执行:./client <服务器IP地址>