嵌入式成长轨迹22 【Linux应用编程强化】【Linux下的C编程 下】【网络编程】
一 网络基础知识
计算机网络是用通信线路和通信设备将分布在不同地点的多台计算机相互连接起来,按照共同的网络协议,共享硬件、软件以及数据资源。
1 OSI参考模型
OSI(Open System Interconnection Reference Model)是国际标准化组织(ISO)于1981年提出的网络系统互连参考模型,它可以实现不同厂家生产的计算机之间的相互连接。OSI参考模型将计算机间的通信划分为7个层次,通过不同层次间的分工与合作,实现计算机间的信息交换。
2 TCP/IP协议栈
TCP/IP(Transmission Control Protocol/Internet Protocol)是当前应用最为广泛的网络协议,它是一个4层的协议系统
这部分的要点是
1).IP协议
2).TCP/UDP协议
3).TCP/IP工作原理
二 套接字编程基础
套接字(Socket)是网络通信的基本操作单元,它提供了不同主机之间进程双向通信的端点。进程在通信之前各自建立一个Socket,通过对Socket的读写操作来实现数据的传输。
常用的Socket类型主要有两种:
数据流套接字(Stream Sockets):提供面向连接的数据传输,保证在传输过程中数据包不会被丢失、破坏或重复,按照一定顺序到达目的端。它是最常用的套接字类型,由TCP协议所支持。数据报套接字(Datagram Sockets):提供非面向连接的数据传输,不保证数据传输的可靠性和顺序性,它由UDP协议所支持。
1 套接字编程原理
套接字编程一般采用客户-服务器模式,即由客户进程向服务器进程发出请求,服务器进程执行被请求的任务并将执行结果返回给客户进程。
2 创建套接字
socket函数用来创建一个Socket描述符,后续的连接建立、数据传输等操作都通过该描述符实现。
int socket(int domain, int type, int protocol);
3 绑定套接字
bind函数用来将套接字与计算机上的一个端口号相绑定,进而在该端口监听服务请求。
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
struct sockaddr
{ unsigned short sa_family;
char sa_data[14];
};
struct sockaddr_in
{
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
4 监听网络端口
listen函数用来将套接字设为监听模式,并在套接字指定的端口上开始监听,以便对到达的服务请求进行处理。
int listen(int sockfd, int backlog);
5 接收连接请求
int accept(int sockfd, void *addr, int *addrlen);
服务器接受连接后,accept函数会返回一个新的Socket描述符,进程可以使用这个新的描述符同客户进程传输数据。
6 建立连接
connect函数用来与服务器建立一个TCP连接
int connect(int sockfd, struct sockaddr *serv_addr, int *addrlen);
7 面向连接的数据传输
send和recv函数用来在面向连接的数据流Socket模式下进行数据传输
int send(int sockfd, const void *msg, int len, unsigned int flags);
int recv(int sockfd, void *buf, int len, unsigned int flags);
8 无连接的数据传输
sendto和recvfrom函数用来在无连接的数据报Socket模式下进行数据传输
int sendto(int sockfd, const void *msg, int len, int flags, const struct sockaddr *to, int *tolen);
int recvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int fromlen);
9 关闭套接字
int close(int sockfd);
int shutdown(int sockfd, int howto);
SHUT_RD:关闭接收信道,进程不能继续从接收缓冲区中接收数据,缓冲区中未读取的数据将被丢弃,但进程仍然可以向发送缓冲区中写入数据;
SHUT_WR:关闭发送信道,进程不能继续向发送缓冲区中写入数据,但缓冲区中未发送的数据会继续发送,进程仍然可以从接收缓冲区中接收数据;
SHUT_RDWR:将发送和接收信道全部关闭。
10 应用实例
三 服务器模型
在网络程序中,通常都是多个客户端对应一个服务器,为了处理多个客户端的请求,对服务器程序就提出了特殊的要求。
Linux系统中服务器模型主要有两种:循环服务器和并发服务器。
循环服务器是指服务器在同一时刻只可以响应一个客户端的请求;
而并发服务器是指服务器在同一个时刻可以响应多个客户端的请求。
1 循环服务器
循环服务器的实现比较简单,我们前面编写的服务器程序都属于循环服务器。1).TCP循环服务器
socket(...); /* 创建套接字 */
bind(...); /* 绑定到某端口上 */
listen(...); /* 监听客户端连接 */
while(1) /* 循环处理客户端的请求 */
{
accept(...); /* 接受一个客户端连接 */
while(1) /* 处理客户端的请求 */
{
recv(...);
process(...);
send(...);
}
close(...); /* 关闭当前客户端连接 */
}
2).UDP循环服务器
socket(...); /* 创建套接字 */
bind(...); /* 绑定到某端口上 */
while(1) /* 循环处理客户端的请求 */
{
recvfrom(...);
process(...); sendto(...);
}
2 并发服务器
并发服务器的思想是每一个客户端的请求并不由服务器进程直接处理,而是创建一个子进程来处理。这可以弥补TCP循环服务器容易出现某个客户端独占服务端缺陷。
socket(...); /* 创建套接字 */
bind(...); /* 绑定到某端口上 */
listen(...); /* 监听客户端连接 */
while(1) /* 循环处理客户端的请求 */
{
accept(...); /* 接受一个客户端连接 */
if(fork(..) == 0) /* 创建子进程 */ {
while(1) /* 处理客户端的请求 */
{
recv(...);
process(...);
send(...);
} close(...); /* 关闭当前客户端连接 */
exit(...); /* 子进程退出 */
}
close(...); /* 父进程关闭连接套接字,准备接受下一个客户端连接 */
}
3 多路复用I/O并发服务器
前面的程序中,我们使用的都是阻塞方式的套接字,这时socket函数创建套接字的默认方式。阻塞主要发生在读(接收)操作、写(发送)操作、建立连接以及接受连接过程中。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout); FD_ZERO(fd_set *set)
FD_SET(int fd, fd_set *set)
FD_CLR(int fd, fd_set *set)
FD_ISSET(int fd, fd_set *set)
socket(...); /* 创建套接字 */
bind(...); /* 绑定到某端口上 */
listen(...); /* 监听客户端连接 */while(1) /* 循环处理客户端的请求 */
{
FD_SET(…); /* 设置监听读写文件描述符 */
switch(select(…)) /* 调用select函数 */ {
case -1: /* 发生错误,退出 */
exit(1);
case 0: /* 超时 */
break; default:
if(…) /* 新的连接请求建立 */
{
accept(...); /* 接受一个客户端连接 */
FD_SET(…); /* 将连接套接字加入到监听文件描述符集合中 */
}
else /* 可读写的为一个已经建立过连接的描述符 */
{
… /* 读写套接字 */
}
}
四 域名系统
由于IP地址难以记忆和识别,人们更习惯于通过域名来访问主机,这就需要是用域名服务器(DNS)来进行域名和IP地址之间的转换。这一节我们简单介绍一下转换的函数。
1 通过域名获取主机信息
struct hostent *gethostbyname(const char *name);
struct hostent
{ char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
2 通过地址获取主机信息
struct hostent *gethostbyaddr(const char *addr, size_t len, int type);
3 获取本地主机信息
int gethostname(char *name, size_t len);
五 常见面试题
常见面试题1:什么是套接字?常用的套接字有哪几种类型?
常见面试题2:什么是并发服务器?它的主要优点是什么?
六 小结
TCP/IP协议是当今广域网和局域网上通用的网络协议,掌握基于TCP/IP的Socket编程是Linux系统下进行程序设计的必备知识。通过Sockets编程,程序员可以跳过复杂的网络底层协议和结构,直接编制与平台无关的应用程序,随着互联网的广泛应用,现已逐渐成为网络编程的通用接口。