unp网络编程卷一
实现简单的获取时间的客户机/服务器程序
本章节出现的一些函数和结构体信息如下:
客户机端:
结构体:structsockaddr_in servaddr;
structsockaddr_in
{
shortsin_family; /* AF_INET(地址族)PF_INET(协议族)*/
unsignedshort sin_port; /* Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/
structin_addr sin_addr; /* Internet address */
unsignedchar sin_zero[8]; /* Same size as struct sockaddr没有实际意义,只是为了跟SOCKADDR结构在内存中对齐*/
};
结构体总共16个字节
长度(8位) | AF_INET(8位) |
端口号(16位) | |
IPV4地址(32位) | |
空余(64位) |
主要函数原型:socket(),bzero(),connect(),read()
其余函数:htons(),inet_port()
函数说明:
int
socket(int
domain, int
type, int
protocol);
函数功能:创建一个套接字并返回一个整数描述符,供后面的函数调用(如connect(),read()
)。
函数返回值:若创建成功则返回一个小整数描述符,若失败则返回一个负值
第一个参数 domain
表示协议类型 一般为AF_INET
第二个参数 type
表示要创建的套接字类型。流套接字类型为SOCK_STREAM
、数据报套接字类型为SOCK_DGRAM
。
第三个参数 protocol
表示应用程序所使用的通信协议,一般取0
。
void bzero(void *s, int n);
函数功能:将一段内存单元清空
第一个参数s
为指向单元的指针
第二个参数位要清空内存的大小
int connect (int sockfd, struct sockaddr * serv_addr, int addrlen);
函数功能:将本机与第二个参数所描述的服务器建立TCP连接。
第一个参数为之前建立的套接字的描述符
第二个参数为想要连接的目的主机的信息结构体的指针
第三个参数为第二个参数结构体的大小
ssize_t read(int sockfd, void *buf, int nbyte);
函数功能:从目的处读取信息(此处为26个字节的时间信息)
函数返回值:正常情况下函数会返回实际读取的字节大小,若发生错误返回-1
第一个参数为之前建立的套接字的描述符
第二个参数为目的缓冲区的首地址
第三个参数为想要读出的数据大小
u_short htons( u_short hostshort);
函数功能:将一个本地端的整数转换为网络端整数
参数 为要需转换的无符号短整数
int inet_pton(int af, const char *src, void *dst);
函数功能:将一个字符形式的ip地址转换为数字
第一个参数表示协议一般为AF_INET或者AF_INET6
第二个参数表示指向字符型ip地址的指针
第三个参数表示存放ip地址转换后的目的地址
客户机程序如下:
#include<unp.h>
int main(int argc, char **argv)
{
int sockfd, n, i = 0;
char recvline[MAXLINE + 1];
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: a.out <IPaddress>");
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9999); /*daytime server*/
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
err_quit("inet_pton error for %s", argv[1]);
if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
err_sys("connect error");
while ( n = read(sockfd, recvline, MAXLINE) > 0)
{
recvline[n] = 0; /* null terminate */
i++;
if(fputs(recvline, stdout) == EOF)
err_sys("fputs error");
}
printf("n= %d \n", i);
if(n < 0)
err_sys("read error");
printf("\n program terminition!\n");
exit(0);
}
服务器端:
主要用到的函数有:bind(),listen(),accept()
其他函数:snprintf(),write()
int bind(int sockfd, const struct sockaddr * my_addr, socklen_t addrlen);
第一个参数为建立的套接字描述符
第二个参数为接受连接主机的信息结构体的指针(INADDR_ANY)表示接受任何ip地址从指定端口访问
第三个参数表示结构体的大小
int listen(int s, int backlog);
第一个参数为建立的监听套接字描述符
第二个参数表示TCP模块允许的已完成三次握手过程(TCP模块完成)但还没来得及被应用程序accept的最大链接数.
int accept(int sockfd, void * addr, int * addrlen);
第一个参数sockfd是和listen()中一样的套接字描述符。
第二个参数addr是个指向局部的数据结构通用地址结构信息的指针。
第三个参数addrlen是个局部的整形变量。
int snprintf(char *str, size_t size, const char *format, ...);
这个函数类似与printf(),只是相较之而言,多了前面两个参数,前面两个参数
第一个表示要输出的目的缓冲区的地址,第二个表示缓冲区的大小
ssize_twrite (int fd,const void * buf,size_t count);
这个函数与read函数类似,不过功能不同
write函数将缓冲区数据写入套接字指向的机器
说明:此处应当注意到,当前的套接字与listen函数中视用的套接字并非同一个,这是由于这两个套接字各有功效,listen套接字只监听窗口但并不涉及传输数据。调用accept()将返回一个新的套接字文件描述符。这样就有两个套接字,原来的一个还在侦听你的那个端口,新的套接字在准备发送和接收数据。
服务器端代码如下:
#include<unp.h>
int main(int argc, char **argv)
{
int listenfd, connfd, i;
struct sockaddr_in servaddr;
char buff[MAXLINE];
time_t ticks;
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);
for( ; ; )
{
connfd = Accept(listenfd, (SA *) NULL, NULL);
ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
for(i = 0; i < strlen(buff); i++)
{
Write(connfd, &buff[i], 1);
}
}
}