要编写网络通信程序,首先了解通信用到的协议.
描述一个网络各个协议层的常用方法是OSI模型, 七层模型. 由顶向下依次是应用层,表示层,会话层,传输层,网络层,数据链路层,和物理层.其中最上三层合并为应用层, 并且顶上三层构造的是用户进程, 而底下四层通常由操作系统内核提供. 顶上三层处理具体网络应用的细节,却对通信细节了解较少,而底下四层处理的是所有通信细节. 套接字编程接口就是从顶上三层进入传输层的接口.
--------
1 应用层
--------
2 表示层
--------
3 会话层 应用层 用户进程(应用层细节)
------------------------ <---套接字接口 --------------
4 传输层 TCP UDP 内核进程(通信细节)
--------
5 网络层 IPv4 IPv6
--------
6 数据链路层
--------
7 物理层
--------
一个简单的客户端和服务器程序
以下是TCP当前时间查询的一个客户程序实现
#include "unp.h"
int main(int argc, char *argv[])
{
int sockfd; //socket函数返回值
int n; //读socket的字节数
char recvline[MAXLINE+1]; //用于保存读取socket返回的数据
struct sockaddr_in servaddr; //网际套接字地址结构
if(argc != 2) err_quit("usage: a.out <IPaddress>");
//socket函数创建一个网际(AF_INET)字节流(SOCK_STREAM)套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) err_sys("socket error");
//填入服务器IP和端口
//bzero与memset类似,不过只有两个参数
//htons: 主机到网络短整数
//inet_pton: 呈现形式到数值
bzero(&servaddr, sizeof(servaddr)); //整个结构清0
servaddr.sin_family = AF_INET; //地址族为AF_INET
servaddr.sin_port = htons(13); //端口13
//将命令行参数的IP格式转换为合适的格式
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) err_quit("inet_pton error for %s", argv[1]);
//使用socket连接服务器
if(connect(sockfd, (SA *)&servaddr, sizeof(servaddr)) < 0) err_sys("connect error");
//读取socket返回的数据
while((n = read(sockfd, recvline, MAXLINE)) > 0){
recvline[n] = 0;
if(fputs(recvline, stdout) == EOF)
err_sys("fputs error");
}
if(n < 0) err_sys("read error");
exit(0);
}
以上程序是与IPv4相关的, sockaddr_in结构, 协议族设置为AF_INET, socket第一个参数是AF_INET. 如果要在IPv6上运行, 需要修改这段代码. 更好的做法是编写协议无关的程序. 并且用户需要以点分十进制格式给出服务器IP, 我们更喜欢用域名.涉及到如何将域名对应到ip, 域名解析
以下是服务器程序
#include "unp.h"
int main(int argc, char *argv[])
{
//listenfd为监听的套接字, connfd为连接的套接字
int listenfd, connfd;
//网际套接字地址结构
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
//监听套接字
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
//初始化server套接字地址结构
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET; //地址族为AF_INET
servaddr.sin_port = htons(13); //端口为13
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //可接受任意ip的client
//绑定bind套接字
Bind(listenfd, (SA *)servaddr, sizeof(servaddr));
//开始监听listen, LISTENQ表示监听描述符上排队的最大客户连接数
Listen(listenfd, LISTENQ);
//以上socket, bind, listen是发生在3次握手之前
for(;;){
//accept调用会阻塞,直到某个client连接到达并被内核接受
//TODO: TCP的三次握手建立连接, 握手完毕时accept返回
// 返回连接socket
connfd = Accept(listenfd, (SA *)NULL, NULL);
ticks = time(NULL);
snprintf(buff, MAXLINE, "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, strlen(buff));
//close关闭连接
//该调用产生TCP连接终止序列: 每个方向上发送一个FIN, 每个FIN又由各自的对端确认
Close(connfd);
}
return 0;
}
该服务器程序也是建立在IPv4的协议上, 服务器程序有几个方面需要注意:
1. 一次只能与一个client建立连接,如果多个连接差不多同时到达, 内核会在最大的数目限制下加入队列, 然后每次返回一个accept.该服务器类型为迭代服务器. 另一种是并发服务器, 可以同时与多个客户建立连接, 最简单的实现就是fork.
2. 从shell命令中启动服务器, 我们需要它运行很长时间, 需要修改代码让它作为守护进程(daemon), 能在后台运行且不跟任何终端关联的进程.
域名解析?
TCP建立连接的三次握手, 终止TCP连接要4个TCP分组?
并发服务器的具体实现?
如何作为Unix守护进程(daemon)?
描述一个网络各个协议层的常用方法是OSI模型, 七层模型. 由顶向下依次是应用层,表示层,会话层,传输层,网络层,数据链路层,和物理层.其中最上三层合并为应用层, 并且顶上三层构造的是用户进程, 而底下四层通常由操作系统内核提供. 顶上三层处理具体网络应用的细节,却对通信细节了解较少,而底下四层处理的是所有通信细节. 套接字编程接口就是从顶上三层进入传输层的接口.
--------
1 应用层
--------
2 表示层
--------
3 会话层 应用层 用户进程(应用层细节)
------------------------ <---套接字接口 --------------
4 传输层 TCP UDP 内核进程(通信细节)
--------
5 网络层 IPv4 IPv6
--------
6 数据链路层
--------
7 物理层
--------
一个简单的客户端和服务器程序
以下是TCP当前时间查询的一个客户程序实现
#include "unp.h"
int main(int argc, char *argv[])
{
int sockfd; //socket函数返回值
int n; //读socket的字节数
char recvline[MAXLINE+1]; //用于保存读取socket返回的数据
struct sockaddr_in servaddr; //网际套接字地址结构
if(argc != 2) err_quit("usage: a.out <IPaddress>");
//socket函数创建一个网际(AF_INET)字节流(SOCK_STREAM)套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) err_sys("socket error");
//填入服务器IP和端口
//bzero与memset类似,不过只有两个参数
//htons: 主机到网络短整数
//inet_pton: 呈现形式到数值
bzero(&servaddr, sizeof(servaddr)); //整个结构清0
servaddr.sin_family = AF_INET; //地址族为AF_INET
servaddr.sin_port = htons(13); //端口13
//将命令行参数的IP格式转换为合适的格式
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) err_quit("inet_pton error for %s", argv[1]);
//使用socket连接服务器
if(connect(sockfd, (SA *)&servaddr, sizeof(servaddr)) < 0) err_sys("connect error");
//读取socket返回的数据
while((n = read(sockfd, recvline, MAXLINE)) > 0){
recvline[n] = 0;
if(fputs(recvline, stdout) == EOF)
err_sys("fputs error");
}
if(n < 0) err_sys("read error");
exit(0);
}
以上程序是与IPv4相关的, sockaddr_in结构, 协议族设置为AF_INET, socket第一个参数是AF_INET. 如果要在IPv6上运行, 需要修改这段代码. 更好的做法是编写协议无关的程序. 并且用户需要以点分十进制格式给出服务器IP, 我们更喜欢用域名.涉及到如何将域名对应到ip, 域名解析
以下是服务器程序
#include "unp.h"
int main(int argc, char *argv[])
{
//listenfd为监听的套接字, connfd为连接的套接字
int listenfd, connfd;
//网际套接字地址结构
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
//监听套接字
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
//初始化server套接字地址结构
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET; //地址族为AF_INET
servaddr.sin_port = htons(13); //端口为13
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //可接受任意ip的client
//绑定bind套接字
Bind(listenfd, (SA *)servaddr, sizeof(servaddr));
//开始监听listen, LISTENQ表示监听描述符上排队的最大客户连接数
Listen(listenfd, LISTENQ);
//以上socket, bind, listen是发生在3次握手之前
for(;;){
//accept调用会阻塞,直到某个client连接到达并被内核接受
//TODO: TCP的三次握手建立连接, 握手完毕时accept返回
// 返回连接socket
connfd = Accept(listenfd, (SA *)NULL, NULL);
ticks = time(NULL);
snprintf(buff, MAXLINE, "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, strlen(buff));
//close关闭连接
//该调用产生TCP连接终止序列: 每个方向上发送一个FIN, 每个FIN又由各自的对端确认
Close(connfd);
}
return 0;
}
该服务器程序也是建立在IPv4的协议上, 服务器程序有几个方面需要注意:
1. 一次只能与一个client建立连接,如果多个连接差不多同时到达, 内核会在最大的数目限制下加入队列, 然后每次返回一个accept.该服务器类型为迭代服务器. 另一种是并发服务器, 可以同时与多个客户建立连接, 最简单的实现就是fork.
2. 从shell命令中启动服务器, 我们需要它运行很长时间, 需要修改代码让它作为守护进程(daemon), 能在后台运行且不跟任何终端关联的进程.
域名解析?
TCP建立连接的三次握手, 终止TCP连接要4个TCP分组?
并发服务器的具体实现?
如何作为Unix守护进程(daemon)?