2017-2018-1 20155308 《信息安全系统设计基础》第十四周学习总结
2017-2018-1 20155308 《信息安全系统设计基础》第十四周学习总结
教材第11章详细总结及书上习题
客户端-服务器编程模型
-
每个网络应用都是基于客户端-服务器模型,一个应用是由一个服务器进程和一个或多个客户端进程组成。服务器管理某种资源,并且通过操作这种资源来为他额客户端提供某种服务。
-
一个客户端-服务器事务由四步组成:
- 当一个客户端需要服务时,向服务器发送一个请求,发起一个事务。
- 服务器收到请求后,解释它,并以适当的方式操作它的资源。
- 服务器给客户端发送一个相应,并等待下一个请求。
- 客户端收到响应并处理它。
网络
- 对主机而言,网络是一种I/O设备,是数据源和数据接收方。
-
物理上而言,网络是一个按照地理远近组成的层次系统。最低层是LAN(局域网),最流行的局域网技术是以太网。
-
每个以太网适配器都有一个全球唯一的48位地址,它存储在适配器的非易失性存储器上。一台主机可以发送一段位到这个网段内其它任何主机。每个帧包括一些固定数量的头部位,用来标识此帧的源和目的地址及帧长此后紧随的就是数据位有效载荷。每个主机适配器都能看到这个帧,但是只有目的主机能读取。
-
互联网重要特性是它能由采用不同技术,互不兼容的局域网和广域网组成,并能使其相互通信。
-
协议提供的两种基本能力
- 命名机制:
- 传送机制:
-
主机和路由器使用互连网络协议在不兼容的局域网间传送数据过程的8个基本步骤:
- 运行在主机A上的客户端进行一个系统调用,从客户端的虚拟地址空间复制数据到内核缓冲区中。
- 主机S上的协议软件通过在数据前附加互联网络包头和LAN1帧头,创建一个LAN1的帧。
- LAN1适配器复制该帧到网络上。
- 当此帧到达路由器时,路由器的LAN1适配器从电缆上读取它,并把它传送到协议软件。
- 路由器从互联网络包头中提取出目的互联网络地址,并用它作为路由表的索引,确定向哪里转发这个包。
- 路由器的LAN2适配器复制该帧到网络上。
- 当此帧到达主机B时,它的适配器从电缆上读到此帧,并将它传送到协议软件。
- 最后,主机B上的协议软件剥落包头和帧头。
全球IP因特网
每台因特网主机都运行实现TCP/IP协议,(传输控制协议/互联网协议)的软件,几乎每个现代计算机系统都支持这个协议。
- 把因特网看做一个世界范围的主机集合,满足以下特性:
- 主机集合被映射为一组32位的IP地址。
- 这组IP地址被映射为一组称为因特网域名的标识符。
- 因特网主机上的进程能够通过连接和任何其他主机上的进程。
- 检索并打印一个DNS主机条目:
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<arpa/inet.h>
int main(int argc,char *argv[])
{
char **pp; /*指向字符串的指针*/
struct in_addr addr; /*存储IP地址的结构体*/
struct hostent *hostp; /*地址结构体*/
/*如果参数小于2,就报错*/
if(argc!=2) {
fprintf(stderr,"usage: %s <domain name or dotted-decimal>\n",argv[0]);
exit(0); /*正常退出程序*/
}
/*如果运行参数为一个点分十进制IP地址,则去根据它检索地址结构体;否则,根据域名检索地址结构体*/
if(inet_aton(argv[1],&addr)!=0)
hostp = gethostbyaddr((const char *)&addr ,sizeof(addr),AF_INET);
else
hos/*打印官方域名*/
printf("official hostname: %s\n",hostp->h_name);
/*打印域名的别名*/
for(pp = hostp->h_aliases;*pp!=NULL;pp++) {
printf("alias: %s\n",*pp);
}
/*打印域名的IP地址,十进制点分形式*/
for(pp = hostp->h_addr_list;*pp!=NULL;pp++) {
addr.s_addr=((struct in_addr *)*pp)->s_addr;
printf("address: %s\n",inet_ntoa(addr));
}
/*程序正常退出*/
exit(0);
}tp = gethostbyname(argv[1]);
IP地址
网络程序将IP地址存放在所示IP地址结构中。
在IP地址结构中存放的地址总是一网络字节顺序存放的,即使主机字节顺序是小端法。Unix提供了下面这样的函数在网络和主机字节顺序见时间转换。
应用程序使用以下函数来实现IP地址和点分十进制串之间的转换。
- 练习题11.1
答:
- 练习题11.2
答:代码如下
#include <stdio.h>
int ctoi(char ch)
{
if(ch >= 'a') return (10 + ch -'a');
return ch - '0';
}
void dex2dd(char *s)
{
int n, flag = 0;
char *p;
p = s + 2;
while(*p) {
n = ctoi(*p) * 16 + ctoi(*++p) * 1;
if(flag > 0) printf(".");
printf("%d", n);
p++; flag++;
}
printf("\n");
}
int main(int argc,char *argv[])
{
dex2dd(argv[1]);
return 0;
}
- 练习题11.3
答:代码如下
#include <stdio.h>
char a[10];
int ctoi(char ch)
{
if(ch >= 'a') return (10 + ch -'a');
return ch - '0';
}
//将十进制的正整数n转换成base进制
void decimal(int n, int base)
{
int r, i = 0;
char c;
if(n < 16) printf("0");
do {
r = n % base;
c = r < 10 ? (r + '0') : ('a' + r - 10);
a[i++] = c;
n = n / base;
} while (n);
while (i) printf("%c", a[--i]);
}
void dd2hex(char *s)
{
char *p, *q;
int i, n, count;
n = 0;
p = q = s;
printf("0x");
while(*q && *p) {
while(*q != '.' && *q) q++;
count = q - p;
for(i = 0; i < count; i++) {
n = n * 10 + ctoi(*p);
p++;
}
decimal(n, 16);
if(*q) {
q++;
p = q;
}
n = 0;
}
printf("\n");
}
int main(int argc,char *argv[])
{
dd2hex(argv[1]);
return 0;
}
因特网域名
- 每台因特网主机都有本地定义的域名localhost,这个域名总是映射为回送地址127.0.0.1
- 使用HOSTNAME来确定本地主机的实际域名
- 最简单的情况中,一个域名和一个IP地址之间一一映射
- 在某些情况下,多个域名可以映射为同一个IP地址
- 最通常情况下,多个域名可以映射到同一组的多个IP地址
6. 某些合法的域名没有映射到任何IP地址
套接字接口
套接字接口是一组函数,它们和Unix I/O函数结合起来,用于建立网络应用。
套接字地址结构
因特网套接字地址存放在类型为sockaddr_in的16字节结构中
函数
- socket函数:客户端和服务器使用socket函数来创建一个套接字描述符
- connect函数:客户端通过调用connect函数来建立和服务器的连接
接下来的套接字函数————bind、listen和accept,服务器用它们来和客户端建立连接
- bind函数
- listen函数
- accept函数
- open_clientfd函数:客户端调用open_clientfd建立与服务器的连接
- open_listenfd函数:调用open_listenfd函数,服务器创建一个监听描述符,准备好接收连接请求。
- 练习题11.4
答:
Web服务器
- Web客户端和服务器之间的交互用的是一个基于文本的应用级协议,叫做 HTTP (Hypertext Transfer Protocol,超文本传输协议)。 HTTP 是一个简单的协议。一个 Web 客户端(即浏览器) 打开一个到服务器的因特网连接,并且请求某些内容。服务器响应所请求的内容,然后关闭连接。浏览器读取这些内容,并把它显示在屏幕上。
- Web内容
- Web 服务器以两种不同的方式向客户端提供内容: •取一个磁盘文件,并将它的内容返回给客户端。磁盘文件称为静态内容 (static content), 而返回文件给客户端的过程称为服务静态内容 (serving static content)。
- 运行一个可执行文件,并将它的输出返回给客户端。运行时可执行文件产生的输出称为态内容 (dynamic content),而运行程序并返回它的输出到客户端的过程称为服务动态内容 (serving dynamic content)。
http事务
因为HTTP是基于在因特网连接上传送的文本行的,我们可以使用Linux的TELNET程序来和因特网上任何web服务器执行事务。
- http请求
组成:一个请求行,后面跟随零个或者更多个请求报头,再跟随一个空的文本行来终止报头列表。
格式:method URI version
- http响应
组成:一个响应行,后面跟随零个或者更多个响应报头,再跟随一个终止报头的空行,再跟随一个响应主体。
格式:version status-code status_message
- 练习题11.5
答:在子进程中运行的CGI程序不需要显式地关闭他的输入输出流。当子进程终止时,内核会自动关闭所有描述符。
TINY Web服务器
1.Tiny的main程序
Tiny是一个迭代服务器,通过命令行中传递来的端口值,调用Open_listenfd()函数打开一个监听套接字,然后Tiny执行无限循环:服务器阻塞在accept,等待监听描述符listenfd上的连接请求,当服务器从accept返回connfd,表明已经与客户端建立起了连接,执行事务,并关闭连接它的那一端,进行下一次循环。
#include "csapp.h"
void doit(int fd);
void read_requesthdrs(rio_t *rp);
int parse_uri(char *uri, char *filename, char *cgiargs);
void serve_static(int fd, char *filename, int filesize);
void get_filetype(char *filename, char *filetype);
void serve_dynamic(int fd, char *filename, char *cgiargs);
void clienterror(int fd, char *cause, char *errnum,
char *shorting,char *longmsg);
int main(int argc,char *argv[])
{
int listenfd,connfd,port,clientlen;
struct sockaddr_in clientaddr;
if(argc != 2)
{
fprintf(stderr,"usage: %s <port>\n",argv[0]);
exit(0);
}
port = atoi(argv[1]);
listenfd = Open_listenfd(port);
while(1)
{
clientlen = sizeof(clientaddr);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
doit(connfd);
Close(connfd);
}
}
2.doit()处理HTTP事务
先了解一下HTTP请求的组成。一个HTTP请求是由一个请求行,后面跟随零个或者更多个请求报头,再跟随一个空的文本行来终止报头列表。
HTTP请求行的格式如下。
HTTP支持许多的方法,包括GET、POST、OPTIONS、HEAD、PUT、DELETE和TRACE。目前Tiny只支持GET方法,GET方法指导服务器生成和返回URI标识的内容。URI是URL的后缀,包括文件名和参数。版本字段表明了该请求遵循的HTTP版本,有HTTP/1.0和HTTP/1.1。
doit()处理HTTP事务。
读取并解析请求行,代码中使用Rio_readlineb()从fd读取一行数据到buf,然后分别写入变量method,uri和version。
Tiny不使用请求报头中的任何信息,使用read_requesthdrs()函数忽略掉报头的信息。
从请求中提取URI信息,使用parse_uri()来从URI中提取文件名和请求参数,并返回值标识静态内容或者动态内容。使用stat()获取文件的状态并将状态保存到sbuf中,执行成功返回0,如果执行失败则会返回-1表示该文件在磁盘上不存在。
如果请求的是静态内容,需要验证该文件是一般文件(st_mode == S_ISREG)并且我们有读权限。如果是我们就向客户端提供静态内容。相似的如果请求的是动态内容,需要验证该文件是可执行文件,如果是我们就向客户端提供动态内容。
void doit(int fd)
{
int is_static;
struct stat sbuf;
char buf[MAXLINE], method[MAXLINE],uri[MAXLINE],version[MAXLINE];
char filename[MAXLINE],cgiargs[MAXLINE];
rio_t rio;
/*read request line and headers*/
Rio_readinitb(&rio, fd);
Rio_readlineb(&rio, buf, MAXLINE);
sscanf(buf, "%s %s %s", method, uri, version);
if(strcasecmp(method,"GET"))
{
clienterror(fd, method, "501","Not Implemented",
"Tiny does not implement this method");
return;
}
read_requesthdrs(&rio);
/*prase URI from GET request*/
is_static = parse_uri(uri, filename, cgiargs);
if(stat(filename, &sbuf) < 0)
{
clienterror(fd, filename, "404","Not Found",
"Tiny couldn't find this file");
return;
}
if(is_static)//server static content
{
if(!(S_ISREG(sbuf.st_mode) || !(S_IRUSR & sbuf.st_mode)))
{
clienterror(fd, filename, "403","Forbidden",
"Tiny couldn't read the file");
return;
}
serve_static(fd, filename, sbuf.st_size);
}
else//server dynamic content
{
if(!(S_ISREG(sbuf.st_mode) || !(S_IXUSR & sbuf.st_mode)))
{
clienterror(fd, filename, "403","Forbidden",
"Tiny couldn't run the CGI program");
return;
}
serve_dynamic(fd, filename, cgiargs);
}
}
3.clienterror()用于检查一些错误
先了解一下HTTP响应行的组成。HTTP响应和HTTP请求是相似的。一个HTTP响应的组成有:一个响应行,后面跟随零个或者更多的响应报头,再跟随一个终止报头的空行,再跟随响应主体。响应行的格式是:
版本字段描述了响应所遵循的HTTP版本。状态码是一个三位的正整数,指明对请求的处理。状态消息给出与错误代码等价的英文描述。
clienterror()发送一个HTTP响应报文个给客户端,在响应行中包含状态码和状态消息,响应主体包含一个HTML文件,来向用户解释错误。
void clienterror(int fd, char *cause, char *errnum,
char *shortmsg, char *longmsg)
{
char buf[MAXLINE], body[MAXBUF];
/*Build the HTTP response body*/
sprintf(body, "<html><title>Tiny Error</title>");
sprintf(body, "%s<body bgcolor=""ffffff"">\r\n",body);
sprintf(body, "%s%s: %s\r\n",body,errnum,shortmsg);
sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
sprintf(body, "%s<hr><em>The Tiny Web Server</em><>\r\n",body);
/*Print the HTTP response*/
sprintf(buf, "HTTP/1.0 %s %s\r\n",errnum, shortmsg);
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-type: text/html\r\n");
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-length: %d\r\n\r\n",(int)strlen(body));
Rio_writen(fd, buf, strlen(buf));
Rio_writen(fd, body, strlen(body));
}
4.read_requesthdrs()来跳过请求报头的信息,直到遇见表示报头结束的空文本行。
void read_requesthdrs(rio_t *rp)
{
char buf[MAXLINE];
Rio_readlineb(rp, buf, MAXLINE);
while(strcmp(buf, "\r\n"))
{
Rio_readlineb(rp, buf, MAXLINE);
printf("%s", buf);
}
return;
}
5.parse_uri()解析URI参数
parse_uri()将URI解析为一个文件名和一个可选的CGI参数字符串。如果是静态内容,我们将清除CGI参数串(即将cgiargs置空),然后将URI转换为一个相对的UNIX路径名,例如./home.html。如果URI是以/结尾的则要在后面添加默认文件名home.html。如果是动态内容,URI中的文件名和CGI参数是以?分割,以此可以抽出CGI参数和文件名。
int parse_uri(char *uri, char *filename, char *cgiargs)
{
char *ptr;
if(!strstr(uri, "cgi-bin"))//static content
{
strcpy(cgiargs, "");
strcpy(filename, ".");
strcat(filename, uri);
if(uri[strlen(uri)-1] == '/')
strcat(uri, "home.html");
return 1;
}
else
{
ptr = index(uri, '?');
if(ptr)
{
strcpy(cgiargs, ptr+1);
*ptr = '\0';
}
else
strcpy(cgiargs, "");
strcpy(filename, ".");
strcat(filename, uri);
return 0;
}
}
6.serve_static()处理静态内容
Tiny提供四种不同的静态内容:HTML文件、无格式文本文件、编码为GIF和JPEG格式的图片。
serve_static()函数发送一个HTTP响应,响应的主体包括一个本地文件的内容。首先,我们检查文件名的后缀来判断文件类型,并在响应报头的Content-type字段中显示出来。随后我们发送响应行和响应报头给客户端,用一个空行来终止报头。
然后我们以只读的方式打开所请求的文件获得文件句柄srcfd,并用mmap函数将文件映射到虚拟存储器空间,代码中是将srcfd指向的文件的前filesize个字节映射到一个地址从srcp开始的只读私有虚拟存储器区域。一旦映射成功我们就可以通过srcp来操作文件不再需要文件句柄了,所以我们关闭srcfd。然后我们使用Rio_writen()将文件传送到客户端。这时静态内容的处理操作已经完成了,我们释放srcp的虚拟存储器映射。
void serve_static(int fd, char *filename, int filesize)
{
int srcfd;
char *srcp, filetype[MAXLINE], buf[MAXBUF];
/*Send response headers to client*/
get_filetype(filename,filetype);
sprintf(buf, "HTTP/1.0 200 OK\r\n");
sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
sprintf(buf, "%sContent-lenght: %d\r\n", buf, filesize);
sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
Rio_writen(fd, buf, strlen(buf));
/*Send response body to client*/
srcfd = Open(filename, O_RDONLY, 0);
srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE,srcfd,0);
close(srcfd);
Rio_writen(fd, srcp, filesize);
Munmap(srcp, filesize);
}
void get_filetype(char *filename, char *filetype)
{
if(strstr(filename, ".html"))
strcpy(filetype, "text/html");
else if(strstr(filename, ".gif"))
strcpy(filetype, "image/gif");
else if(strstr(filename, ".jpg"))
strcpy(filetype, "image/jpeg");
else
strcpy(filetype, "text/plain");
}
7.serve_dynamic()处理动态内容
Tiny通过派生一个子进程并在子进程中运行一个CGI程序,来提供各种类型的动态内容。
程序中首先发送一个表明成功的响应行,同时还包括带有信息的Server报头给客户端。CGI程序负责发送响应的剩余部分。
然后我们派生一个子进程,子进程用来自请求URI的CGI参数初始化QUERY_STRING环境变量,CGI程序会通过这个变量获取CGI参数值。接下来,子进程重定向标准输出到已连接文件描述符,然后加载并运行CGI程序。因为CGI程序运行在子进程的上下文中,它能够访问所有在调用execve函数之前就存在的打开文件和环境变量。因此CGI程序写到标准输出上的数据都会直接送到客户端进程,不会受到来自父进程的干涉。旗舰父进程阻塞在wait()的调用中,当子进程终止时,回收操作系统分配给子进程的资源。
void serve_dynamic(int fd, char *filename, char *cgiargs)
{
char buf[MAXLINE], *emptylist[]={NULL};
/*Return first part of HTTP response*/
sprintf(buf, "HTTP/1.0 200 OK\r\n");
Rio_writen(fd, buf,strlen(buf));
sprintf(buf, "Server: Tiny Web Server\r\n");
Rio_writen(fd, buf,strlen(buf));
if(Fork()==0)
{
setenv("QUERY_STRING", cgiargs, 1);
Dup2(fd, STDOUT_FILENO);
Execve(filename, emptylist,environ);
}
Wait(NULL);
}
- 将完整的代码编译
gcc tinyserver.c -o tinyserver
. 执行tinyserver程序并指定所用port(1024--49151可用,其它为系统使用。一般不能占用)
./tinyserver 2000
3. 在浏览器中地址栏输入訪问地址
http:localhost:2000/cgi-bin/adder?30&72
- 执行结果
浏览器中显示:
后台server信息显示:
Socket编程练习
练习一
基于TCP实现
流程
server代码
01.#include <stdio.h>
02.#include <sys/types.h>
03.#include <sys/socket.h>
04.#include <netinet/in.h>
05.#include <arpa/inet.h>
06.
07.
08.int main(int argc, char *argv[])
09.{
10. int server_sockfd;//服务器端套接字
11. int client_sockfd;//客户端套接字
12. int len;
13. struct sockaddr_in my_addr; //服务器网络地址结构体
14. struct sockaddr_in remote_addr; //客户端网络地址结构体
15. int sin_size;
16. char buf[BUFSIZ]; //数据传送的缓冲区
17. memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零
18. my_addr.sin_family=AF_INET; //设置为IP通信
19. my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上
20. my_addr.sin_port=htons(8000); //服务器端口号
21.
22. /*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/
23. if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
24. {
25. perror("socket error");
26. return 1;
27. }
28.
29.
30. /*将套接字绑定到服务器的网络地址上*/
31. if(bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)
32. {
33. perror("bind error");
34. return 1;
35. }
36.
37. /*监听连接请求--监听队列长度为5*/
38. if(listen(server_sockfd,5)<0)
39. {
40. perror("listen error");
41. return 1;
42. };
43.
44. sin_size=sizeof(struct sockaddr_in);
45.
46. /*等待客户端连接请求到达*/
47. if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0)
48. {
49. perror("accept error");
50. return 1;
51. }
52. printf("accept client %s/n",inet_ntoa(remote_addr.sin_addr));
53. len=send(client_sockfd,"Welcome to my server/n",21,0);//发送欢迎信息
54.
55. /*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/
56. while((len=recv(client_sockfd,buf,BUFSIZ,0))>0))
57. {
58. buf[len]='/0';
59. printf("%s/n",buf);
60. if(send(client_sockfd,buf,len,0)<0)
61. {
62. perror("write error");
63. return 1;
64. }
65. }
66.
67.
68. /*关闭套接字*/
69. close(client_sockfd);
70. close(server_sockfd);
71.
72. return 0;
73.}
client代码
01.#include <stdio.h>
02.#include <sys/types.h>
03.#include <sys/socket.h>
04.#include <netinet/in.h>
05.#include <arpa/inet.h>
06.
07.int main(int argc, char *argv[])
08.{
09. int client_sockfd;
10. int len;
11. struct sockaddr_in remote_addr; //服务器端网络地址结构体
12. char buf[BUFSIZ]; //数据传送的缓冲区
13. memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零
14. remote_addr.sin_family=AF_INET; //设置为IP通信
15. remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址
16. remote_addr.sin_port=htons(8000); //服务器端口号
17.
18. /*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/
19. if((client_sockfd=socket(PF_INET,SOCK_STREAM,0))<0)
20. {
21. perror("socket error");
22. return 1;
23. }
24.
25. /*将套接字绑定到服务器的网络地址上*/
26. if(connect(client_sockfd,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr))<0)
27. {
28. perror("connect error");
29. return 1;
30. }
31. printf("connected to server/n");
32. len=recv(client_sockfd,buf,BUFSIZ,0);//接收服务器端信息
33. buf[len]='/0';
34. printf("%s",buf); //打印服务器端信息
35.
36. /*循环的发送接收信息并打印接收信息(可以按需发送)--recv返回接收到的字节数,send返回发送的字节数*/
37. while(1)
38. {
39. printf("Enter string to send:");
40. scanf("%s",buf);
41. if(!strcmp(buf,"quit")
42. break;
43. len=send(client_sockfd,buf,strlen(buf),0);
44. len=recv(client_sockfd,buf,BUFSIZ,0);
45. buf[len]='/0';
46. printf("received:%s/n",buf);
47. }
48.
49. /*关闭套接字*/
50. close(client_sockfd);
51.
52. return 0;
53.}
基于UDP实现
流程
server代码
01.#include <stdio.h>
02.#include <sys/types.h>
03.#include <sys/socket.h>
04.#include <netinet/in.h>
05.#include <arpa/inet.h>
06.
07.int main(int argc, char *argv[])
08.{
09. int server_sockfd;
10. int len;
11. struct sockaddr_in my_addr; //服务器网络地址结构体
12. struct sockaddr_in remote_addr; //客户端网络地址结构体
13. int sin_size;
14. char buf[BUFSIZ]; //数据传送的缓冲区
15. memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零
16. my_addr.sin_family=AF_INET; //设置为IP通信
17. my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上
18. my_addr.sin_port=htons(8000); //服务器端口号
19.
20. /*创建服务器端套接字--IPv4协议,面向无连接通信,UDP协议*/
21. if((server_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0)
22. {
23. perror("socket error");
24. return 1;
25. }
26.
27. /*将套接字绑定到服务器的网络地址上*/
28. if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0)
29. {
30. perror("bind error");
31. return 1;
32. }
33. sin_size=sizeof(struct sockaddr_in);
34. printf("waiting for a packet.../n");
35.
36. /*接收客户端的数据并将其发送给客户端--recvfrom是无连接的*/
37. if((len=recvfrom(server_sockfd,buf,BUFSIZ,0,(struct sockaddr *)&remote_addr,&sin_size))<0)
38. {
39. perror("recvfrom error");
40. return 1;
41. }
42. printf("received packet from %s:/n",inet_ntoa(remote_addr.sin_addr));
43. buf[len]='/0';
44. printf("contents: %s/n",buf);
45.
46. /*关闭套接字*/
47. close(server_sockfd);
48.
49. return 0;
50.}
client代码
01.#include <stdio.h>
02.#include <sys/types.h>
03.#include <sys/socket.h>
04.#include <netinet/in.h>
05.#include <arpa/inet.h>
06.
07.int main(int argc, char *argv[])
08.{
09. int client_sockfd;
10. int len;
11. struct sockaddr_in remote_addr; //服务器端网络地址结构体
12. int sin_size;
13. char buf[BUFSIZ]; //数据传送的缓冲区
14. memset(&remote_addr,0,sizeof(remote_addr)); //数据初始化--清零
15. remote_addr.sin_family=AF_INET; //设置为IP通信
16. remote_addr.sin_addr.s_addr=inet_addr("127.0.0.1");//服务器IP地址
17. remote_addr.sin_port=htons(8000); //服务器端口号
18.
19. /*创建客户端套接字--IPv4协议,面向无连接通信,UDP协议*/
20. if((client_sockfd=socket(PF_INET,SOCK_DGRAM,0))<0)
21. {
22. perror("socket error");
23. return 1;
24. }
25. strcpy(buf,"This is a test message"); // 发送的内容
26. printf("sending: '%s'/n",buf);
27. sin_size=sizeof(struct sockaddr_in);
28.
29. /*向服务器发送数据包*/
30. if((len=sendto(client_sockfd,buf,strlen(buf),0,(struct sockaddr *)&remote_addr,sizeof(struct sockaddr)))<0)
31. {
32. perror("recvfrom");
33. return 1;
34. }
35.
36. /*关闭套接字*/
37. close(client_sockfd);
38.
39. return 0;
40.}
练习二
TCP下传文件
server代码
#include<netinet/in.h> // sockaddr_in
#include<sys/types.h> // socket
#include<sys/socket.h> // socket
#include<stdio.h> // printf
#include<stdlib.h> // exit
#include<string.h> // bzero
#define SERVER_PORT 8000
#define LENGTH_OF_LISTEN_QUEUE 20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
int main(void)
{
// 声明并初始化一个服务器端的socket地址结构
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htons(INADDR_ANY);
server_addr.sin_port = htons(SERVER_PORT);
// 创建socket,若成功,返回socket描述符
int server_socket_fd = socket(PF_INET, SOCK_STREAM, 0);
if(server_socket_fd < 0)
{
perror("Create Socket Failed:");
exit(1);
}
int opt = 1;
setsockopt(server_socket_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定socket和socket地址结构
if(-1 == (bind(server_socket_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))))
{
perror("Server Bind Failed:");
exit(1);
}
// socket监听
if(-1 == (listen(server_socket_fd, LENGTH_OF_LISTEN_QUEUE)))
{
perror("Server Listen Failed:");
exit(1);
}
while(1)
{
// 定义客户端的socket地址结构
struct sockaddr_in client_addr;
socklen_t client_addr_length = sizeof(client_addr);
// 接受连接请求,返回一个新的socket(描述符),这个新socket用于同连接的客户端通信
// accept函数会把连接到的客户端信息写到client_addr中
int new_server_socket_fd = accept(server_socket_fd, (struct sockaddr*)&client_addr, &client_addr_length);
if(new_server_socket_fd < 0)
{
perror("Server Accept Failed:");
break;
}
// recv函数接收数据到缓冲区buffer中
char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
if(recv(new_server_socket_fd, buffer, BUFFER_SIZE, 0) < 0)
{
perror("Server Recieve Data Failed:");
break;
}
// 然后从buffer(缓冲区)拷贝到file_name中
char file_name[FILE_NAME_MAX_SIZE+1];
bzero(file_name, FILE_NAME_MAX_SIZE+1);
strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));
printf("%s\n", file_name);
// 打开文件并读取文件数据
FILE *fp = fopen(file_name, "r");
if(NULL == fp)
{
printf("File:%s Not Found\n", file_name);
}
else
{
bzero(buffer, BUFFER_SIZE);
int length = 0;
// 每读取一段数据,便将其发送给客户端,循环直到文件读完为止
while((length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0)
{
if(send(new_server_socket_fd, buffer, length, 0) < 0)
{
printf("Send File:%s Failed./n", file_name);
break;
}
bzero(buffer, BUFFER_SIZE);
}
// 关闭文件
fclose(fp);
printf("File:%s Transfer Successful!\n", file_name);
}
// 关闭与客户端的连接
close(new_server_socket_fd);
}
// 关闭监听用的socket
close(server_socket_fd);
return 0;
}
client端
#include<netinet/in.h> // sockaddr_in
#include<sys/types.h> // socket
#include<sys/socket.h> // socket
#include<stdio.h> // printf
#include<stdlib.h> // exit
#include<string.h> // bzero
#define SERVER_PORT 8000
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512
int main()
{
// 声明并初始化一个客户端的socket地址结构
struct sockaddr_in client_addr;
bzero(&client_addr, sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = htons(INADDR_ANY);
client_addr.sin_port = htons(0);
// 创建socket,若成功,返回socket描述符
int client_socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(client_socket_fd < 0)
{
perror("Create Socket Failed:");
exit(1);
}
// 绑定客户端的socket和客户端的socket地址结构 非必需
if(-1 == (bind(client_socket_fd, (struct sockaddr*)&client_addr, sizeof(client_addr))))
{
perror("Client Bind Failed:");
exit(1);
}
// 声明一个服务器端的socket地址结构,并用服务器那边的IP地址及端口对其进行初始化,用于后面的连接
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
if(inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr) == 0)
{
perror("Server IP Address Error:");
exit(1);
}
server_addr.sin_port = htons(SERVER_PORT);
socklen_t server_addr_length = sizeof(server_addr);
// 向服务器发起连接,连接成功后client_socket_fd代表了客户端和服务器的一个socket连接
if(connect(client_socket_fd, (struct sockaddr*)&server_addr, server_addr_length) < 0)
{
perror("Can Not Connect To Server IP:");
exit(0);
}
// 输入文件名 并放到缓冲区buffer中等待发送
char file_name[FILE_NAME_MAX_SIZE+1];
bzero(file_name, FILE_NAME_MAX_SIZE+1);
printf("Please Input File Name On Server:\t");
scanf("%s", file_name);
char buffer[BUFFER_SIZE];
bzero(buffer, BUFFER_SIZE);
strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name));
// 向服务器发送buffer中的数据
if(send(client_socket_fd, buffer, BUFFER_SIZE, 0) < 0)
{
perror("Send File Name Failed:");
exit(1);
}
// 打开文件,准备写入
FILE *fp = fopen(file_name, "w");
if(NULL == fp)
{
printf("File:\t%s Can Not Open To Write\n", file_name);
exit(1);
}
// 从服务器接收数据到buffer中
// 每接收一段数据,便将其写入文件中,循环直到文件接收完并写完为止
bzero(buffer, BUFFER_SIZE);
int length = 0;
while((length = recv(client_socket_fd, buffer, BUFFER_SIZE, 0)) > 0)
{
if(fwrite(buffer, sizeof(char), length, fp) < length)
{
printf("File:\t%s Write Failed\n", file_name);
break;
}
bzero(buffer, BUFFER_SIZE);
}
// 接收成功后,关闭文件,关闭socket
printf("Receive File:\t%s From Server IP Successful!\n", file_name);
close(fp);
close(client_socket_fd);
return 0;
}