Linux应用开发—网络通信
1 网络通信概述
Linux下的网络编程,我们一般称为 socket 编程,socket 是内核向应用层提供的一套网络编程接口,我们可以基于socket接口开发自己的网络相关应用程序。
1.1 socket 简介
套接字(socket)是Linux下的一种进程间通信机制(socket IPC),使用 socket IPC 可以使得在不同主机上的应用程序之间进行通信(网络通信),也可以是同一台主机上的不同应用程序。socket IPC 通常使用客户端-服务器这种模式完成通信,多个客户端可以同时连接到服务器中,与服务器完成数据交互。
内核向应用层提供了 socket 接口,我们只需要调用 socket 接口开发我们的应用程序即可。socket 是应用层与 TCP/IP 协议通信的中间软件抽象层,它是一组接口,它把复杂的 TCP/IP 协议隐藏在接口后面。因此,我们无需深入理解 TCP/UDP 等复杂的 TCP/IP 协议,socket 已经为我们封装好了,我们只需要遵循 socket 的规定去编程,写出的程序自然遵循 TCP/UDP 标准。
1.2 IP 和 端口
所有的数据传输,都要包含三个元素:源、目的和长度。
在网络通信中我们使用 “IP 和 端口” 来表示源和目的。
1.3 网络传输中的 2 个对象:server 和 client
我们访问网站时会涉及到2个对象:网站服务器和浏览器。网站服务器平时安静地呆着,浏览器主动发起数据请求。网站服务器、浏览器可以抽象为 2 个软件的概念:server 程序、client 程序。
1.4 计算机网络体系结构
计算机网络体系结构分为3种:OSI体系结构(七层),TCP/IP体系结构(四层),五层体系结构。
- OSI体系结构:概念清楚,理论也比较完整,但是它既复杂又不实用。
- TCP/IP体系结构:TCP/IP是一个四层体系结构,得到了广泛的运用。
- 五层体系结构:为了方便学习,折中OSI体系结构和TCP/IP体系结构,综合二者的优点,这样既简洁,又能将概念讲清楚。
TCP/IP与OSI最大的不同在于:OSI是一个理论上的网络通信模型,而TCP/IP则是实际运行的网络协议。
1.4.1 五层网络体系结构概述
-
应用层:它是体系结构中的最高层,直接为用户的应用进程(例如电子邮件、文件传输和终端仿真)提供服务。在因特网中的应用层协议很多,如支持万维网应用的 HTTP 协议,支持电子邮件的 SMTP 协议,支持文件传送的 FTP 协议,DNS,POP3,SNMP,Telnet 等等。
-
运输层:负责向两个主机中进程之间的通信提供服务。运输层主要使用以下两种协议:
- 传输控制协议 TCP:面向连接的,数据传输的单位是报文段,能够提供可靠的交付。
- 用户数据包协议 UDP:无连接的,数据传输的单位是用户数据报,不保证提供可靠的交付,只能提供“尽最大努力交付”。
-
网络层:负责将被称为数据包(datagram)的网络层分组从一台主机移动到另一台主机。
-
链路层:因特网的网络层通过源和目的地之间的一系列路由器路由数据报。
-
物理层:在物理层上所传数据的单位是比特。物理层的任务就是透明地传送比特流。
我们并不需要具体理解这些层,我们只需要使用“运输层”编写应用程序。使用“运输层”时,可以选择 TCP 协议,也可以选择 UDP 协议。
1.4.2 TCP 和 UDP 区别
- TCP是面向连接的协议,而UDP是无连接的协议。
- TCP提供可靠的数据传输,UDP则不提供可靠性保证。
- TCP提供了可靠性保证,它的数据传输速度相对较慢。UDP没有额外的操作,所以传输速度相对较快。
1.4.3 为何存在UDP协议
既然 TCP 可以提供可靠的数据服务,而 UDP 却不提供,那我们是否可以只用 TCP 呢?
答案是否定的,举个例子:视频通话时,使用 UDP,偶尔的丢包、偶尔的花屏时可以忍受的;如果使用 TCP,每个数据包都要确保可靠传输,当它出错时就重传,这会导致后续的数据包被阻滞,视频效果反而不好。
1.4.4 TCP/UDP 网络通信交互图
2 TCP实现
2.1 服务端
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#define SERVERPORT 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 iClientNum = -1;
int iRecvLen;
unsigned char ucRecvBuf[1000];
signal(SIGCHLD,SIG_IGN); //解决僵尸进程问题
iSocketServer = socket(AF_INET,SOCK_STREAM,0); //打开套接字
if(iSocketServer == -1)
{
printf("socket error!\n");
return -1;
}
memset(&tSocketServerAddr, 0x0, sizeof(struct sockaddr_in)); //清零
//填充变量
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
tSocketServerAddr.sin_port = htons(SERVERPORT);
//将地址与套接字进行关联、绑定
iRet = bind(iSocketServer,(const struct sockaddr *)&tSocketServerAddr,sizeof(struct sockaddr));
if(iRet == -1)
{
printf("bind error!\n");
return -1;
}
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)
{
iClientNum++;
printf("Get connect from client %d : %s\n",iClientNum,inet_ntoa(tSocketClientAddr.sin_addr));
if(!fork())
{
/********子进程源码**********/
while (1)
{
/********接受客户端发来的数据并显示出来**********/
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);
}
}
}
}
}
close(iSocketServer); //关闭套接字
return 0;
}
2.2客户端
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVERPORT 8888
int main(int argc,char* argv[])
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iRet;
unsigned char ucSendBuf[1000];
int iSendLen;
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;
}
memset(&tSocketServerAddr, 0x0, sizeof(struct sockaddr_in)); //清零
//填充变量
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVERPORT);
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n");
return -1;
}
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))
{
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0); //发送数据
if (iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}
close(iSocketClient); //关闭套接字
return 0;
}
2.3 实验
- 编译代码:
gcc -o server ./server.c
gcc -o client ./client.c
- 运行服务端:
./server
- 查看本机可用ip:
ifconfig
- 运行客户端
./client 192.168.217.128
并且可以看到服务端连接到客户端。
- 发送数据
在服务器端显示接收到的内容。
3 UDP实现
3.1 服务端
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define SERVERPORT 8888
int main(int argc,char* argv[])
{
int iSocketServer;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRet;
int iAddrLen;
int iRecvLen;
unsigned char ucRecvBuf[1000];
iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);//打开套接字
if (iSocketServer == -1)
{
printf("socket error!\n");
return -1;
}
memset(&tSocketServerAddr, 0x0, sizeof(struct sockaddr_in)); //清零
//填充变量
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
tSocketServerAddr.sin_port = htons(SERVERPORT);
//将地址与套接字进行关联、绑定
iRet = bind(iSocketServer,(const struct sockaddr *)&tSocketServerAddr,sizeof(struct sockaddr));
if(iRet == -1)
{
printf("bind error!\n");
return -1;
}
while (1)
{
iAddrLen = sizeof(struct sockaddr);
iRecvLen = recvfrom(iSocketServer,ucRecvBuf,999,0,(struct sockaddr *)&tSocketClientAddr,&iAddrLen);
if(iRecvLen > 0)
{
ucRecvBuf[iRecvLen] = '\0';
printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
}
}
close(iSocketServer);
return 0;
}
3.2 客户端
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define SERVERPORT 8888
int main(int argc,char* argv[])
{
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
int iRet;
unsigned char ucSendBuf[1000];
int iSendLen;
if (argc != 2)
{
printf("Usage:\n");
printf("%s <server_ip>\n", argv[0]);
return -1;
}
iSocketClient = socket(AF_INET, SOCK_DGRAM, 0); //打开套接字
if (iSocketClient == -1)
{
printf("socket error!\n");
return -1;
}
memset(&tSocketServerAddr, 0x0, sizeof(struct sockaddr_in)); //清零
//填充变量
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVERPORT);
if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
{
printf("invalid server_ip\n");
return -1;
}
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))
{
iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0); //发送数据
if (iSendLen <= 0)
{
close(iSocketClient);
return -1;
}
}
}
close(iSocketClient);
return 0;
}
3.3 实验
- 编译代码:
gcc -o server ./server.c
gcc -o client ./client.c
- 运行服务端
./server
- 运行客户端
./client 192.168.217.128
- 发送数据
在服务器端显示接收到的内容。