网络编程_pthread_fork
1.查看while源代码
前言
- TCP(传输控制协议)
TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。
- 嵌套字
套接字是通信的基石,是支持
TCP/IP
协议的路通信的基本操作单元。可以将套接字看作不同主机间的进程进行双间通信的端点,它构成了单个主机内及整个网络间的编程界面。套接字存在于通信域中,通信域是为了处理一般的线程通过套接字通信而引进的一种抽象概念。套接字通常和同一个域中的套接字交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序),各种进程使用这个相同的域互相之间用Internet协议簇来进行通信。
系统调用
- socket
- 原型:
int socket(int domain, int type,int protocol)
参数 | 解释 | 用法 |
---|---|---|
domain | 网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等). | AF_UNIX只能够用于单一的Unix 系统进程间通信,而AF_INET是针对Internet的,因而可以允许在远程主机之间通信(当我们 man socket时发现 domain可选项是 PF_而不是AF_,因为glibc是posix的实现所以用PF代替了AF,不过我们都可以使用的). |
type | 网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等) | SOCK_STREAM表明使用的是TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流.SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信. |
protocol | 由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了 |
说明:socket为网络通讯做基本的准备.成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况.
- bind
- 原型:
int bind(int sockfd, struct sockaddr *my_addr, int addrlen)
参数 | 解释 |
---|---|
sockfd | 由socket调用返回的文件描述符 |
addrlen | sockaddr结构的长度 |
my_addr | 一个指向sockaddr的指针. 在中有 sockaddr的定义 |
- 不过由于系统的兼容性,我们一般不用这个头文件,而使用另外一个结构
(struct sockaddr_in)
来代替.在中有sockaddr_in
的定义
struct sockaddr_in{
unsigned short sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
- 我们主要使用
Internet
所以
sin_family一般为AF_INET
,
sin_addr
设置为INADDR_ANY
表示可以和任何的主机通信,
sin_port
是我们要监听的端口号.sin_zero[8]
是用来填充的. 说明:bind将本地的端口同socket返回的文件描述符捆绑在一起.成功是返回0,失败的情况和socket一样 .
- listen
- 原型:
int listen(int sockfd,int backlog)
参数 | 解释 |
---|---|
sockfd | 是bind后的文件描述符 |
backlog | 设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示可以介绍的排队长度. |
说明: listen
函数将bind
的文件描述符变为监听套接字.返回的情况和bind
一样.
- accept
- 原型:
int accept(int sockfd, struct sockaddr *addr,int *addrlen)
参数 | 解释 |
---|---|
sockfd | |
addr/addrlen | 是用来给客户端的程序填写的,服务器端只要传递指针就可以了 |
bind,listen和accept是服务器端用的函数,accept调用时,服务器端的程序会一直阻塞到有一个 客户程序发出了连接. accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了. 失败时返回-1
- connect
- 原型:
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen)
参数 | 解释 |
---|---|
sockfd | socket返回的文件描述符 |
serv_addr | 储存了服务器端的连接信息.其中sin_add是服务端的地址 |
addrlen | serv_addr的长度 |
说明:connect
函数是客户端用来同服务端连接的.成功时返回0,sockfd
是同服务端通讯的文件描述符 失败时返回-1.
2.编译并在ubuntu下运行
- 编译
gcc -o server-while-tcp.out server-while-tcp.c
gcc -o client.out client.c
- 运行
./server-while-tcp.out
./client.out 192.168.1.*** //新的窗口打开
3.修改服务器为多线程模式
功能
1.多线程发送数据,即一个服务器对应多个客户端,最多可以同时监听10个客户端。
2.客户端向服务器发起连接请求,成功后打印连接信息。
3.客户端向服务器发送数据后,服务器端打印数据。
4.服务器将得到的数据全部转换为大写后返回到客户端。
5.客户端打印出从服务器端返回的数据。
server(服务器端)源码
#include <sys/types.h> /* 网络编程所需头文件*/
#include <sys/socket.h> /* 网络编程所需头文件*/
#include <string.h>
#include <netinet/in.h> /* 包含类似inet_ntoa函数的头文件 */
#include <arpa/inet.h> /* 包含类似inet_ntoa函数的头文件 */
#include <unistd.h> /* 包含close函数、forck函数等系统调用函数的头文件 */
#include <stdio.h>
#include <signal.h> /* 包含signal函数的头文件 */
#define SERVER_PORT 8888 /* 监听端口号 */
#define BACKLOG 10 /* listen函数中最大同时监听连接路数 */
/* socket
* bind
* listen
* accept
* send/recv
*/
int charup(unsigned char ch[1000]);
int main(int argc, char **argv)
{
int iSocketServer;
int iSocketClient;
struct sockaddr_in tSocketServerAddr; /* 存放服务器端的通讯协议族、要监听的端口号等信息的结构体 */
struct sockaddr_in tSocketClientAddr; /* 存放连接的客户端的IP地址等信息的结构体 */
int iRet;
int iAddrLen;
int iRecvLen;
unsigned char ucRecvBuf[1000];
int iClientNum = -1;
signal(SIGCHLD, SIG_IGN); /* 处理僵死进程,如果不加,被关闭的客户端所创建的服务器端子进程的资源将不会被父进程回收,导致资源浪费 */
iSocketServer = socket(AF_INET, SOCK_STREAM, 0); /* 创建socket */
if (-1 == iSocketServer)
{
printf("Socket error!\n");
return -1;
}
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* 将short型的端口数据转换成适合于网络传输的数据类型 */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; /* 允许和任何的主机通信 */
memset(tSocketServerAddr.sin_zero, 0, 8); /* 内存置0,确保和struct sockaddr的长度相同 */
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); /* 之前创建的socket文件描述符将被bind修饰 */
if (iRet == -1)
{
printf("Bind error!\n");
return -1;
}
iRet = listen(iSocketServer, BACKLOG); /* 调用listen函数来监听 */
if (-1 == iRet)
{
printf("listen error!\n");
return -1;
}
else
{
printf("Listening & Accepting...\n");
}
while (1)
{
iAddrLen = sizeof(struct sockaddr);
/* 调用accept函数来等待客户端来连接,客户连接成功返回一个值,连接失败返回-1; */
iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
if (-1 != iSocketClient)
{
iClientNum++;
/* 支持多个客户端连接,每有一个就调用fork(),并创建一个子进程 */
printf("Get connnect from NO.%d : %s\n", iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
if (!fork()) /* 执行到fork()后马上复制一个代码完全一样的子进程*/
{ /* 父进程走fork()=0;子进程走fork()!=0; */
/*子进程的源码*/
while (1)
{
/* 接受客户端发来的数据并显示出来 */
iRecvLen = iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
if (iRecvLen <= 0)
{
close(iSocketClient); /* 一直接受客户端传来的消息 */
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0'; /* 加上结束符 */
printf("Get Msg From client %d : %s\n", iClientNum, ucRecvBuf);
}
charup(ucRecvBuf); /* 字符串转大写字母函数 */
iRecvLen = send(iSocketClient, ucRecvBuf, strlen(ucRecvBuf), 0);
if(iRecvLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}
}
}
close(iSocketServer);
return 0;
}
// 定义字符串转大写字母函数
int charup(unsigned char ch[1000])
{
int i = 0;
while (ch[i] != '\0')
{
if(ch[i]>='a'&&ch[i]<='z')
ch[i]=ch[i]-32; //如果你忘记了大小写之间相差32的值,也可以用'a'-'A'来表示
i ++;
}
return 0;
}
client(客户端)源码
#include <sys/types.h> /* 网络编程所需头文件*/
#include <sys/socket.h> /* 网络编程所需头文件*/
#include <string.h>
#include <netinet/in.h> /* 包含类似inet_ntoa函数的头文件 */
#include <arpa/inet.h> /* 包含类似inet_ntoa函数的头文件 */
#include <unistd.h> /* 包含close函数、forck函数等系统调用函数的头文件 */
#include <stdio.h>
#include <signal.h>
#define SERVER_PORT 8888 //同一端口号
/* socket //系统调用
* connect
* listen
* send/recv
*/
int main(int argc, char **argv)
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iRet; //返回值
unsigned char ucSendBuf[1000];
unsigned char ucRecvBuf[1000];
int iSendLen;
int iRecvLen;
if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
iSocketClient = socket(AF_INET, SOCK_STREAM, 0);
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); //host to net,short
//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; //本机上所有IP
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 (-1 == iRet)
{
printf("connect error!\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
while (1)
{
if(fgets(ucSendBuf, 999, stdin))
{
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
if(iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
iRecvLen = iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
if (iRecvLen <= 0)
{
close(iSocketClient); //一直接受客户端传来的消息
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0'; //加上结束符
printf("Feedback:%s\n", ucRecvBuf);
}
}
return 0;
}
编译并在ubuntu下运行
- 编译
gcc -o srv srv.c
gcc -o cli cli.c
- 运行
./srv
./cli 192.168.1.*** //新的窗口打开
4.[选做]提高项
使用${CROSS_COMPILE}
编译各个程序
必须先安装交叉编译器,并配置环境变量。
- 参考
https://www.cnblogs.com/jackyim/p/3553824.html
- 检查:终端输入arm-,按两下tap
- 配置环境(临时)
export ARCH=arm
export CROSS_COMPILE=arm-linux-gnueabihf-
export PATH=$PATH:/home/suliu/qemu/100ask/100ask_imx6ull-qemu/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin
- 编译
arm-linux-gnueabihf-gcc -o server.o server.c //arm下运行的程序
arm-linux-gnueabihf-gcc -o client.o client.c
gcc -o server.o server.c //ubuntu下运行的程序
gcc -o client.o client.c
在开发板内mount nfs
,并运行各个程序;理解运行状态
主机需要安装nfs
- 参考
https://blog.csdn.net/sl_97/article/details/123792844?spm=1001.2014.3001.5501
服务器端(ubuntu主机)
- 创建根目录和共享挂载点
sudo mkdir -p /srv/nfs4/www
- s将挂载目录绑定到共享挂载点
sudo mount --bind /home/suliu/Desktop/tcp/ /srv/nfs4/www
- 导出文件系统
- 打开/etc/exports文件
sudo vim /etc/exports
- 添加以下行
/srv/nfs4 127.0.0.1(rw,nohide,insecure,no_subtree_check,async,no_root_squash)
/srv/nfs4/www 127.0.0.1(rw,nohide,insecure,no_subtree_check,async,no_root_squash)
- 保存文件并导出共享
sudo exportfs -ar
- 查看当前活动的导出及其状态
exportfs -v
/srv/nfs4 127.0.0.1(rw,async,wdelay,nohide,insecure,no_root_squash,no_subtree_check,sec=sys,rw,insecure,no_root_squash,no_all_squash)
/srv/nfs4/www 127.0.0.1(rw,async,wdelay,nohide,insecure,no_root_squash,no_subtree_check,sec=sys,rw,insecure,no_root_squash,no_all_squash)
- 在服务器端执行下面命令,强制重新读取
/etc/exports
文件
[root@localhost log]# exportfs -ra
[root@localhost log]# exportfs
不执行这一条可能会挂载失败
客户端(开发板)
开发板已经内置了nfs,不用安装
- 运行开发板
cd /home/suliu/qemu/ubuntu-18.04_imx6ul_qemu_system
./qemu-imx6ull-gui.sh
-
登录(root)
-
查看
IP
ifconfig
ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.1.133 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::d8af:2afa:14a3:d96f prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:64:d3:91 txqueuelen 1000 (Ethernet)
RX packets 80442 bytes 77555991 (77.5 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 40343 bytes 7869307 (7.8 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 31049 bytes 3439747 (3.4 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 31049 bytes 3439747 (3.4 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
开发板默认给主机分配的ip是:
10.0.2.2
- 挂载(mount)
将主机上的
/srv/nfs4/www
挂载到开发板的/root/mnt/
下
mount -t nfs -o nolock,vers=4 10.0.2.2:srv/nfs4/www /root/mnt/
- 查看
df -h
cd /mnt/
ls
运行程序(开发板做客户端,主机做服务器)
#服务器(ubuntu)执行.out文件(gcc)
book@book-virtual-machine:/srv/nfs4/www$ ./server.out
#客户端(ubuntu)执行.o文件(arm-linux-gnueabihf-gcc)
[root@qemu_imx6ul:~/mnt]# ./client.o 10.0.2.2
注意:退出程序时,应该先退出客户端,再退出服务器,以免再次启动时连接失败。(有时服务器退出后需要等待30s-5min才能再次运行)
echo sever
要求:在源程序的基础上加上一个应答,发送什么返回什么。
思路:由于我们之前已经实现了多线程的通信,这里给
server.c
程序加上一个send
,直接将缓存区的数据原封不动的发送给客户端即可;再给客户端加上一个accpt
函数,并将收到的数据打印出来即可。
- 服务端改进
/* 在服务端打印数据后面加上 */
iRecvLen = send(iSocketClient, ucRecvBuf, strlen(ucRecvBuf), 0);
if(iRecvLen <= 0)
{
close(iSocketClient);
return -1;
}
/* 在客户端发送数据后面加上 */
iRecvLen = iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
if (iRecvLen <= 0)
{
close(iSocketClient); /*一直接受服务器传来的消息*/
return -1;
}
else
{
ucRecvBuf[iRecvLen] = '\0'; /* 加上结束符 */
printf("Feedback:%s\n", ucRecvBuf);
}
echo大写
要求:客户端发送一个数据,服务器端打印,将数据(字符串)转换为大写,并返回到客户端,客户端打印数据
思路:只需在
echo server
例程的基础上给sever
加入一个数据处理函数即可。
/* 一个完整字符串大写转换函数 */
#include<stdio.h>
int main()
{
char ch[100];
int i;
while(scanf("%c",&ch[i])!=EOF)
{
if(ch[i]>='a'&&ch[i]<='z')
ch[i]=ch[i]-32;
printf("%c",ch[i]);
}
return 0;
}
/* 修改为我们使用的函数 */
int charup(unsigned char ch[1000]); //声明
charup(ucRecvBuf); //调用
int charup(unsigned char ch[1000])
{
int i = 0;
while (ch[i] != '\0')
{
if(ch[i]>='a'&&ch[i]<='z')
ch[i]=ch[i]-32;
i ++;
}
return 0;
}
引用
- https://baike.baidu.com/item/%E5%A5%97%E6%8E%A5%E5%AD%97/9637606
- https://baike.baidu.com/item/TCP/33012
- https://blog.csdn.net/com852
- https://blog.csdn.net/bryanwang_3099/article/details/106358013?spm=1001.2101.3001.6650.9&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-9.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-9.pc_relevant_default&utm_relevant_index=18
- https://blog.csdn.net/sl_97/article/details/123792844?spm=1001.2014.3001.5501
- https://forums.100ask.net/