文章目录
1.网络通信
了解网络通信,可以看如下视频:
半小时了解网络通信
1.1 网络通信概述
使用GIT下载资料后,视频中涉及的文档和图片,位于如下目录:
01_all_series_quickstart\
04_嵌入式Linux应用开发基础知识\
doc_pic\08.网络编程
source\12_socket\
1.1.1 IP和端口
所有的数据传输,都有三个要素 :源、目的、长度。怎么表示源或者目的呢?请看下图:
所以,在网络传输中需要使用“IP和端口”来表示源或目的。
1.1.2 网络传输中的2个对象:server和client
我们经常访问网站,这涉及2个对象:网站服务器,浏览器。网站服务器平时安静地呆着,浏览器主动发起数据请求。网站服务器、浏览器可以抽象成2个软件的概念:server程序、client程序。
1.1.3 两种传输方式:TCP/UDP
在一般的网络书籍中,网络协议被分为5层,如下图所示:
应用层:它是体系结构中的最高层,直接为用户的应用进程(例如电子邮件、文件传输和终端仿真)提供服务。
在因特网中的应用层协议很多,如支持万维网应用的HTTP协议,支持电子邮件的SMTP协议,支持文件传送的FTP协议,DNS,POP3,SNMP,Telnet等等。
运输层:负责向两个主机中进程之间的通信提供服务。
运输层主要使用以下两种协议:
(1) 传输控制协议TCP(Transmission Control Protocol):面向连接的,数据传输的单位是报文段,能够提供可靠的交付。
(2) 用户数据包协议UDP(User Datagram Protocol):无连接的,数据传输的单位是用户数据报,不保证提供可靠的交付,只能提供“尽最大努力交付”。
网络层:负责将被称为数据包(datagram)的网络层分组从一台主机移动到另一台主机。
链路层:因特网的网络层通过源和目的地之间的一系列路由器路由数据报。
物理层:在物理层上所传数据的单位是比特。物理层的任务就是透明地传送比特流。
这些层对于初学者来说很难理解,我们只需要知道:我们需要使用“运输层”编写应用程序,我们的应用程序位于“应用层”。
使用“运输层”时,可以选择TCP协议,也可以选择UDP协议。
-
TCP和UDP 原理上的区别
TCP向它的应用程序提供了面向连接的服务。这种服务有2个特点:可靠传输、流量控制(即发送方/接收方速率匹配)。它包括了应用层报文划分为短报文,并提供拥塞控制机制。
UDP协议向它的应用程序提供无连接服务。它没有可靠性,没有流量控制,也没有拥塞控制。 -
为何存在UDP协议
既然TCP提供了可靠数据传输服务,而UDP不能提供,那么TCP是否总是首选呢?
答案是否定的,因为有许多应用更适合用UDP,举个例子:视频通话时,使用UDP,偶尔的丢包、偶尔的花屏时可以忍受的;如果使用TCP,每个数据包都要确保可靠传输,当它出错时就重传,这会导致后续的数据包被阻滞,视频效果反而不好。
使用UDP时,有如下特点:
a. 关于何时发送什么数据控制的更为精细
采用UDP时只要应用进程将数据传递给UDP,UDP就会立即将其传递给网络层。而TCP有重传机制,而不管可靠交付需要多长时间。但是实时应用通常不希望过分的延迟报文段的传送,且能容忍一部分数据丢失。
b. 无需建立连接,不会引入建立连接时的延迟。
c. 无连接状态,能支持更多的活跃客户。
d. 分组首部开销较小。 -
TCP/UDP网络通信大概交互图
下面我们分别画出运用TCP协议和运用UDP协议的客户端和服务器大概交互图。
1.2 网络编程主要函数介绍
1.2.1 socket函数
int socket(int domain, int type,int protocol);
此函数用于创建一个套接字。
domain
是网络程序所在的主机采用的通讯协族(AF_UNIX
和AF_INET
等)。
AF_UNIX
只能够用于单一的Unix
系统进程间通信,而AF_INET
是针对Internet
的,因而可以允许远程通信使用。
type
是网络程序所采用的通讯协议(SOCK_STREAM
,SOCK_DGRAM
等)。
SOCK_STREAM
表明用的是TCP
协议,这样会提供按顺序的,可靠,双向,面向连接的比特流。
SOCK_DGRAM
表明用的是UDP
协议,这样只会提不可靠,无连接的通信。
关于protocol
,由于指定了type
,所以这个地方一般只要用0
来代替就可以了。
此函数执行成功时返回文件描述符,失败时返回-1
,看errno
可知道出错的详细情况。
1.2.2 bind函数
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
从函数用于将地址绑定到一个套接字。
sockfd
是由socket
函数调用返回的文件描述符。
my_addr
是一个指向sockaddr
的指针。
addrlen
是sockaddr
结构的长度。
sockaddr
的定义:
struct sockaddr{
unisgned short as_family;
char sa_data[14];
};
不过由于系统的兼容性,我们一般使用另外一个结构(struct sockaddr_in
) 来代替。
sockaddr_in
的定义:
struct sockaddr_in{
unsigned short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}
如果使用Internet
所以sin_family
一般为AF_INET
。
sin_addr
设置为INADDR_ANY
表示可以和任何的主机通信。
sin_port
是要监听的端口号。
bind
将本地的端口同socket
返回的文件描述符捆绑在一起.成功是返回0,失败的情况和socket
一样。
1.2.3 listen函数
int listen(int sockfd,int backlog);
此函数宣告服务器可以接受连接请求。
sockfd
是bind
后的文件描述符。
backlog
设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
listen
函数将bind
的文件描述符变为监听套接字,返回的情况和bind
一样。
1.2.4 accept函数
int accept(int sockfd, struct sockaddr *addr,int *addrlen);
服务器使用此函数获得连接请求,并且建立连接。
sockfd
是listen
后的文件描述符。
addr
,addrlen
是用来给客户端的程序填写的,服务器端只要传递指针就可以了, bind
,listen
和accept
是服务器端用的函数。
accept
调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接。 accept
成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了,失败时返回-1 。
1.2.5 connect函数
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);
可以用connect
建立一个连接,在connect
中所指定的地址是想与之通信的服务器的地址。
sockfd
是socket
函数返回的文件描述符。
serv_addr
储存了服务器端的连接信息,其中sin_add
是服务端的地址。
addrlen
是serv_addr
的长度
connect
函数是客户端用来同服务端连接的.成功时返回0,sockfd
是同服务端通讯的文件描述符,失败时返回-1。
1.2.6 send函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd
指定发送端套接字描述符;
buf
指明一个存放应用程序要发送数据的缓冲区;
len
指明实际要发送的数据的字节数;
flags
一般置0。
客户或者服务器应用程序都用send
函数来向TCP
连接的另一端发送数据
1.2.7 recv函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd
指定接收端套接字描述符;
buf
指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
len
指明buf
的长度;
flags
一般置0
。
客户或者服务器应用程序都用recv
函数从TCP
连接的另一端接收数据。
1.2.8 recvfrom函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
recvfrom
通常用于无连接套接字,因为此函数可以获得发送者的地址。
src_addr
是一个struct sockaddr
类型的变量,该变量保存源机的IP地址及端口号。
addrlen
常置为sizeof
(struct sockaddr
)。
1.2.9 sendto函数
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
sendto
和send
相似,区别在于sendto
允许在无连接的套接字上指定一个目标地址。
dest_addr
表示目地机的IP地址和端口号信息,
addrlen
常常被赋值为sizeof
(struct sockaddr
)。
sendto
函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。
1.3 TCP编程
本章配套的视频,我没有重新录制,而是修改这个视频得来:2012年录制的“第3期项目视频”中的“第1课第6.1节_一小时学会网络编程_两个简单例子_tcp_udp”。
在2012年录制的视频,质量一点都不比现在的差。
悦己之作,方能悦人。很高兴,我从来没降低要求。
使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\
04_嵌入式Linux应用开发基础知识\source\12_socket\
tcp\
/*socket
*bind
*listen
*accept
*send/recv
*/
//1.把头文件包含进来,再写
//本段代码大多数函数头文件
#include<sys/types>
#include <sys/socket.h>
//meset头文件
#include <string.h>
//ntoa函数头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//fork函数的头文件
#include <unistd.h>
#include <stdio.h>
#define SERVER_PORT 8888
int main(int argc,char **argv)
{
int iSocketServer;
int iRet;
struct sockaddr_in tSocketServerAddr;
int iAddrlen;
int iSocketClient;
unsigned char ucRecvBuf[1000];
int iRecvLen;
int iClientNum = 0;
tSocketServerAddr.sin_family = AF_INFT;
tSocketServerAddr.sin_port = htons( SERVER_pORT;) //host to net,short"将主机字节序转换为网络字节序。
tSocketServerAddr.sin_addr.s_addr=INADDR_ANY;//监听主机sever上所有的ip,因为服务器是被动的响应请求。
memset(tSocketSeverAddr.sin_zero,0,8);
iSocketServer = int socket(AF_INET,SOCK_STREAM,0);
if(-1 == iSocketServer)
{
printf("socket error!\n");
return -1;
}
iRet = bind(iSocketSever,(const struct sockaddr *)&tSocketServerAddr,sizeof(struct sockaddr));
/*不过由于系统的兼容性,我们一般不用这个头文件,而使用其他一个结构体(struct sockaddr_in)来代替,在中有sockaddr_in的定义:
struct sockaddr_in{
unsigned short sin_family;
unsigned short sin_port;
struct in_addr sin_add;
unsigned char sin_zero[8];
}
我们主要使用Internet所以sin_family一般为AF_INET,
sin_addr设置为INADDR_ANY表示可以和任何的主机通信。
sin_port是要监听端口号,
sin_zero[8]是用来填充的
bind 将本地的端口同socket返回的文件描述捆绑在一起。成功是返回0,失败的情况和socket一样。
*/
if(-1==iRet)
{
printf("bind error!\n");
return -1;
}
iRet = listen(iSocketServer,BACKLOG);
if(-1 == iRet)
{
printf("listen error!\n");
return -1;
}
while(1)
{
iAddrlen = sizeof(struct sockaddr);
iSocketClient = accept(iSocketServer,(struct sockaddr *)&tSocketClientAddr,&iAddrlen);//等待一条连接
if(-1 != iSocketClient)
{
iClientNum++;//当多台客户端访问的时候
printf("Get connect from client %d : %s\n",iClientNum,inet ntoa(tSocketClientAddr.sin_addr));
if(!fork())//每来一个连接的时候,都使用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;
}
//本段代码大多数函数头文件
#include<sys/types>
#include <sys/socket.h>
//meset头文件
#include <string.h>
//ntoa函数头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//fork函数的头文件
#include <unistd.h>
#include <stdio.h>
/*socket
*connect
*send/recv
*
*/
#define SERVER_PORT 8888
int main( int argc,char **argv){
int iSocketClient;
int iRet;
struct sockaddr_in tSocketServerAddr;
unsigned char ucSendBuf[1000];
int iSendLen;
if(argc!=2)//如果传输参数不是两个,则报错并输出服务器ip
{
printf("Usage:\n");
printf("%s <server_ip\n>",argv[0]);
return -1;
}
iSocketClient = socket(AF_INET,SOCK_STREAM,0);
tSocketServerAddr.sin_family = AF_INFT;
tSocketServerAddr.sin_port = htons( SERVER_pORT;) //host to net,short"将主机字节序转换为网络字节序。
//tSocketServerAddr.sin_addr.s_addr=INADDR_ANY;//监听主机sever上所有的ip,因为服务器是被动的响应请求。
memset(tSocketSeverAddr.sin_zero,0,8);
if(0 == inet_aton(argv[1],&tSocketServerAddr.sin_addr))//上面那个代码,是服务器监视其他客户端的代码,而这段是把客户端的ip传送给服务器的代码
{
printf("invaild server_ip\n");
return -1;
}
iRet = connect(iSocketClient,(const struct sockaddr *)&tSocketServerAddr,sizeof(struct sockaddr));
if(-1 == iRet)
{
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;
}
}
}
/*不过由于系统的兼容性,我们一般不用这个头文件,而使用其他一个结构体(struct sockaddr_in)来代替,在中有sockaddr_in的定义:
struct sockaddr_in{
unsigned short sin_family;
unsigned short sin_port;
struct in_addr sin_add;
unsigned char sin_zero[8];
}
我们主要使用Internet所以sin_family一般为AF_INET,
sin_addr设置为INADDR_ANY表示可以和任何的主机通信。
sin_port是要监听端口号,
sin_zero[8]是用来填充的
bind 将本地的端口同socket返回的文件描述捆绑在一起。成功是返回0,失败的情况和socket一样。
*/
return 0;
}
1.4 UDP编程
本章配套的视频,我没有重新录制,而是修改这个视频得来:2012年录制的“第3期项目视频”中的“第1课第6.1节_一小时学会网络编程_两个简单例子_tcp_udp”。
在2012年录制的视频,质量一点都不比现在的差。
悦己之作,方能悦人。很高兴,我从来没降低要求。
使用GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\
04_嵌入式Linux应用开发基础知识\source\12_socket\
udp\
udp2\
#include <sys/types.h> /* See NOTES */
#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/recv
*/
#define SERVER_PORT 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);
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
//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 (-1 == iRet)
{
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;
}
}
}
return 0;
}
#include <sys/types.h> /* See NOTES */
#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>
#include <signal.h>
/* socket
* bind
* sendto/recvfrom
*/
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int iSocketServer;
int iSocketClient;
struct sockaddr_in tSocketServerAddr;
struct sockaddr_in tSocketClientAddr;
int iRet;
int iAddrLen;
int iRecvLen;
unsigned char ucRecvBuf[1000];
int iClientNum = -1;
iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == iSocketServer)
{
printf("socket error!\n");
return -1;
}
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net, short */
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
memset(tSocketServerAddr.sin_zero, 0, 8);
iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
if (-1 == iRet)
{
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;
}