一,Linux C++ Socket网络编程
1.什么是TCP/IP、UDP?
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。
下面的图表明了这些协议的关系。
2.Socket在哪里呢?
3.Socket是什么呢?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
门面模式,用自己的话说,就是系统对外界提供单一的接口,外部不需要了解内部的实现。
4.有很多的框架,为什么还在从Socket开始?
现在的跨平台网络编程框架很多,如Java的SSH,C/C++的Boost等。
现在的分布式框架很多,如Hadoop等。
Socket是分布式、云计算、网络编程的基础,对Socket的学习有利于对其他框架的理解。
下图是Socket编程的基本流程:
第一步:建立一个socket
int socket(int af, int type, int protocol)
A. 'int af'代表地址族或者称为socket所代表的域,通常有两个选项:
1. AF_UNIX - 只在单机上使用。
2. AF_INET - 可以在单机或其他使用DARPA协议(UDP/TCP/IP)的异种机通信。
B. 'int type'代表你所使用的连接类型,通常也有两种情况:
1. SOCK_STREAM - 用来建立面向连接的sockets,可以进行可靠无误的的数据传输
2. SOCK_DGRAM - 用来建立没有连接的sockets,不能保证数据传输的可靠性。
C. 'int protocol'通常设定为0。使系统选择默认的由协议族和连接类型所确定的协议。
D. 返回值是一个文件描述句柄,如果在此期间发生错误则返回-1并且设定了相应的errno。
int sockfd; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket 创建出错!"); exit(1); }
第二步:绑定名字socket: bind()
int bind(int sockfd, struct sockaddr *name, int namelen)
A. sockfd是从socket()调用得到的文件描述句柄。
B. name是一个指向sockaddr类型结构的一个指针。
C. namelen给出了文件名的具体长度。
在使用AF_INET地址族的时候,类型定义如下:
struct sockaddr_in { short int sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; };
在这个结构种,name.sin_family应当被设定为AF_INET
struct sockaddr_in name; int sockfd; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { exit(1); } name.sin_family = AF_INET; name.sin_port = htons(8087); name.sin_addr.s_addr = htonl(INADDR_ANY); bzero(&(name.sin_zero), 8); /* zero out the rest of the space */ if (bind(sockfd, (struct sockaddr *) &name, sizeof(struct sockaddr)) == -1) { exit(1); }
现在,如果没有问题的话,我们建立的socket就有一个名字了!相反,如果不成功,它会设定相应的错误代码,并使程序退出。
第三步:远程连接: connect()
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen)
A. sockfd是我们建立的文件描述句柄
B. serv_addr是一个sockaddr结构,包含目的的地址和端口号
C. addrlen 被设定为sockaddr结构的大小。
if (connect(sockfd, (struct sockaddr *) &name, sizeof(name)) < 0) { exit(1); }
第四步:监听: listen()
int listen(int sockfd, int backlog)
B. 参数backlog是指一次可以监听多少个连接
if (listen(sockfd, 20) == -1) { exit(1); }
第五步:阻塞接受连接: accept()
当有人试图从我们打开的端口登陆进来时,我们应该响应他,这个时候就要用到accept()函数了。
int accept(int sockfd, void *addr, int *addrlen)
conn = accept(sockfd, (struct sockaddr*) &name, sizeof(name)); if (conn < 0) { exit(1); }
第六步:输入和输入的完成: send() and recv()
int send(int sockfd, const void *msg, int len, int flags)
int recv(int sockfd, void *buf, int len, unsigned int flags)
send():
sockfd - socket file descriptor
msg - message to send
len - size of message to send
flags - read 'man send' for more info, set it to 0 for now
recv():
sockfd - socket file descriptor
buf - data to receive
len - size of buf
flags - same as flags in send()
注意:如果使用的连接类型是SOCK_DGRAM,那么应该使用sendto()和recvfrom()来实现数据传输。
char buffer[BUFFER_SIZE]; while (1) { memset(buffer, 0, sizeof(buffer)); int len = recv(conn, buffer, sizeof(buffer), 0); if (strcmp(buffer, "exit ") == 0) break; fputs(buffer, stdout); send(conn, buffer, len, 0); }
结束: close() and shutdown()
当传输结束时,应当关闭连接。
一个服务端等待, 客户端上传文件到服务端,通过输入要上传的文件名,目前只做到仅对当前执行文件的目录下的文件,应该在服务端收到文件路径之后进行处理的。
服务端代码:
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <netdb.h> #include <arpa/inet.h>//for inet_ntoa #include <unistd.h>//for fork() #define SERVER_PORT 6666 #define LISTEN_QUEUE 20 #define BUFFER_SIZE 1024 #define FILE_NAME_MAX_SIZE 512 int main() { //设置一个socket地址结构server_addr,代表服务器internet地址, 端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); //把一段内存区的内容全部设置为0 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htons(INADDR_ANY); server_addr.sin_port = htons(SERVER_PORT); //创建用于internet的流协议(TCP)socket,用server_socket代表服务器socket int server_socket = socket(PF_INET, SOCK_STREAM, 0); if (server_socket < 0) { printf("Create Socket Failed!"); exit(1); } //把socket和socket地址结构联系起来 if (bind(server_socket, (struct sockaddr*) &server_addr, sizeof(server_addr))) { printf("Server Bind Port: %d Failed! ", SERVER_PORT); exit(1); } //server_socket用于监听 if (listen(server_socket, LISTEN_QUEUE)) { printf("Server Listen Failed!"); exit(1); } while (1) { //定义客户端的socket地址结构client_addr char buffer[BUFFER_SIZE]; struct sockaddr_in client_addr; socklen_t length = sizeof(client_addr); int client_socket = accept(server_socket, (struct sockaddr*) &client_addr, &length); if (client_socket < 0) { printf("Server Accept Failed! "); break; } bzero(buffer, BUFFER_SIZE); // 获取客户端要传输的文件名 length = recv(client_socket, buffer, BUFFER_SIZE, 0); if (length < 0) { printf("Server Recieve Data Failed! "); break; } 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)); // 新建文件 FILE * fp = fopen(file_name, "w"); if (NULL == fp) { printf("File: %s CAN NOT WRITE! ", file_name); } else { bzero(buffer, BUFFER_SIZE); int file_block_length = 0; while ((file_block_length = recv(client_socket, buffer, BUFFER_SIZE, 0)) > 0) { if (file_block_length < 0) { printf("Recieve Data From Client Failed! "); break; } int write_length = fwrite(buffer, sizeof(char), file_block_length, fp); if (write_length < file_block_length) { printf("File: %s Write Failed ", file_name); break; } bzero(buffer, BUFFER_SIZE); } fclose(fp); printf("File: %s Transfer Finished ", file_name); } close(client_socket); } close(server_socket); return 0; }
客户端
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <netdb.h> #include <arpa/inet.h>//for inet_ntoa #include <unistd.h>//for fork() #define SERVER_PORT 6666 #define BUFFER_SIZE 1024 #define FILE_NAME_MAX_SIZE 512 int main() { //设置一个socket地址结构client_addr,代表客户机internet地址, 端口 struct sockaddr_in client_addr; bzero(&client_addr, sizeof(client_addr)); //把一段内存区的内容全部设置为0 client_addr.sin_family = AF_INET; //internet协议族 client_addr.sin_addr.s_addr = htons(INADDR_ANY); //INADDR_ANY表示自动获取本机地址 client_addr.sin_port = htons(0); //0表示让系统自动分配一个空闲端口 //创建用于internet的流协议(TCP)socket,用client_socket代表客户机socket int client_socket = socket(AF_INET, SOCK_STREAM, 0); if (client_socket < 0) { printf("Create Socket Failed! "); exit(1); } //把客户机的socket和客户机的socket地址结构联系起来 if (bind(client_socket, (struct sockaddr*) &client_addr, sizeof(client_addr))) { printf("Client Bind Port Failed! "); exit(1); } //设置一个socket地址结构server_addr,代表服务器的internet地址, 端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); socklen_t server_addr_length = sizeof(server_addr); // 向服务器发起连接,连接成功后client_socket代表了客户机和服务器的一个socket连接 if (connect(client_socket, (struct sockaddr*) &server_addr, server_addr_length) < 0) { exit(1); } // 连接上服务器, 选择要上传的文件 char file_name[FILE_NAME_MAX_SIZE + 1]; bzero(file_name, FILE_NAME_MAX_SIZE + 1); printf("Please Input File Name Upload To Server: "); 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)); FILE * fp = fopen(file_name, "r"); if (NULL == fp) { printf("File: %s NOT FOUND! ", file_name); exit(1); } // 发送文件名 int nameLength = send(client_socket, buffer, BUFFER_SIZE, 0); if (nameLength < 0) { printf("File name Error! "); exit(0); } bzero(buffer, BUFFER_SIZE); int file_block_length = 0; while ((file_block_length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0) { if (send(client_socket, buffer, file_block_length, 0) < 0) { printf("Send File: %s Failed ", file_name); break; } bzero(buffer, BUFFER_SIZE); } printf("File: %s Transfer Finished ", file_name); fclose(fp); close(client_socket); return 0; }
计算机网络实验:Linux Apache下模拟服务器解析验证HTTP-Get请求并发回网页数据
HTTP请求格式:
<request-line>
<headers>
<blank line>
[<request-body>]
说明:第一行必须是一个请求行(request-line),用来说明请求类型,要访问的资源以及所使用的HTTP版本.
紧接着是一个首部(header)小节,用来说明服务器要使用的附加信息.
之后是一个空行.
再后面可以添加任意的其他数据[称之为主体(body)].
例1 GET请求:
GET / HTTP/1.1
Accept: */*
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Host: www.google.cn
Connection: Keep-Alive
说明:请求的第一部分说明了该请求是一个GET请求.该行的第二部分是一个斜杠(/),用来说明请求的是该域名的根目录.该行的最后一部分说明使用的是HTTP1.1版本(另一个可选荐是1.0).
第2行是请求的第一个首部,HOST将指出请求的目的地.User-Agent,服务器端和客户端脚本都能访问它,它是浏览器类型检测逻辑的重要基础.该信息由你的浏览器来定义,并且在每个请求中自动发送.Connection,通常将浏览器操作设置为Keep-Alive
第三部分,空行,即使不存在请求主体,这个空行也是必需的.
例2 POST请求:
POST / HTTP1.1
Host:www.wrox.com
User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Content-Type:application/x-www-form-urlencoded
Content-Length:40
Connection: Keep-Alive
name=Professional%20Ajax&publisher=Wiley
说明:请求行开始处的GET改为POST,以表示不同的请求类型.
Content-Type说明了请求主体的内容是如何编码的.浏览器始终以application/x-www-form-urlencoded的格式编码来传送数据,这是针对简单URL编码的MIME类型.Content-Length说明了请求主体的字节数.
最后请求主体.名称-值对的形式.
HTTP响应格式:
<status-line>
<headers>
<blank line>
[<response-body>]
HTTP/1.1 200 OK
Date: Fri, 22 May 2009 06:07:21 GMT
Content-Type: text/html; charset=UTF-8<html>
<head></head>
<body>
<!--body goes here-->
</body>
</html>
说明:HTTP状态码200,找到资源,并且一切正常.
Date:生成响应的日期和时间.
Content-Type:指定了MIME类型的HTML(text/html),编码类型是UTF-8
HTML源文体.
程序代码与简要分析
数据包分析:得到请求的文件,用户名与密码
int analysis_packet(char *packet, char *file, char *ID, char *psw)
HTTP数据包头 202根据时间与数据包内容大小构建,404返回无法连接
int headerBuilder(char *msg, int status, int size)
与本机Java写成的运行的ServerSocket连接本机MySQL数据库校验用户名与密码
bool check(char *ID, char *psw)
主函数,作为服务端监听本机3334端口的数据请求,根据数据包请求校验与分析。
ServerSocket在本机8081端口监听,为Java与MySQL交互的服务端,对每个Socket请求开辟一个线程处理,返回校验结果。
#include <cstdio> #include <stdlib.h> #include <errno.h> #include <cstring> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <time.h> #include <arpa/inet.h>//for inet_ntoa #include <unistd.h>//for fork() #define MAXN 20480 struct INFO { char ID[50], psw[50]; }; int analysis_packet(char *packet, char *file, char *ID, char *psw) { char *p, *q; if ((p = strtok(packet, " ")) == NULL) return 0; if (strcmp(p, "GET") == 0) { //GET if ((p = strtok(NULL, " ")) == NULL) return 1; q = p; if ((p = strtok(NULL, " ")) != NULL && strcmp(p, "HTTP/1.1") == 0) { //协议HTTP/1.1 if ((q = strtok(q, "?")) != NULL) { strcpy(file, q); //文件名 if ((q = strtok(NULL, "?")) != NULL) { char *tmp; if ((tmp = strtok(q, "&")) != NULL) { strcpy(ID, tmp + 3); } if ((tmp = strtok(NULL, "&")) != NULL) { strcpy(psw, tmp + 3); } return 2; } } return 1; } else printf("未知的协议! "); } else printf("未知的请求! "); return 0; } int headerBuilder(char *msg, int status, int size) { char num[5]; char *wday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; char *wmon[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; struct tm *ptr; time_t it; time(&it); ptr = localtime(&it); //取得本地时间 switch (status) { case 200: //状态行 strcat(msg, "HTTP/1.1 200 OK "); //首部 strcat(msg, "Date: "); strcat(msg, wday[ptr->tm_wday]); strcat(msg, ", "); sprintf(num, "%d", ptr->tm_mday); strcat(msg, num); strcat(msg, " "); strcat(msg, wmon[ptr->tm_mon]); strcat(msg, " "); sprintf(num, "%d", 1900 + ptr->tm_year); strcat(msg, num); strcat(msg, " "); sprintf(num, "%d", ptr->tm_hour); strcat(msg, num); strcat(msg, ":"); sprintf(num, "%d", ptr->tm_min); strcat(msg, num); strcat(msg, ":"); sprintf(num, "%d", ptr->tm_sec); strcat(msg, num); strcat(msg, " GMT "); strcat(msg, "Content-Type: text/html;charset=utf-8 "); strcat(msg, "Content-Length: "); sprintf(num, "%d", size); strcat(msg, num); strcat(msg, " "); break; case 404: //状态行 strcat(msg, "HTTP/1.1 404 Not Found "); //首部 strcat(msg, "Date: "); strcat(msg, wday[ptr->tm_wday]); strcat(msg, ", "); sprintf(num, "%d", ptr->tm_mday); strcat(msg, num); strcat(msg, " "); strcat(msg, wmon[ptr->tm_mon]); strcat(msg, " "); sprintf(num, "%d", 1900 + ptr->tm_year); strcat(msg, num); strcat(msg, " "); sprintf(num, "%d", ptr->tm_hour); strcat(msg, num); strcat(msg, ":"); sprintf(num, "%d", ptr->tm_min); strcat(msg, num); strcat(msg, ":"); sprintf(num, "%d", ptr->tm_sec); strcat(msg, num); strcat(msg, " GMT "); break; } return 0; } bool check(char *ID, char *psw) { printf("check info %s %s ", ID, psw); int cfd = socket(AF_INET, SOCK_STREAM, 0); int recbytes; char buffer[50] = { '