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\n") == 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!\n", 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!\n"); break; } bzero(buffer, BUFFER_SIZE); // 获取客户端要传输的文件名 length = recv(client_socket, buffer, BUFFER_SIZE, 0); if (length < 0) { printf("Server Recieve Data Failed!\n"); 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!\n", 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!\n"); break; } int write_length = fwrite(buffer, sizeof(char), file_block_length, fp); if (write_length < file_block_length) { printf("File: %s Write Failed\n", file_name); break; } bzero(buffer, BUFFER_SIZE); } fclose(fp); printf("File: %s Transfer Finished\n\n", file_name); } close(client_socket); } close(server_socket); return 0; }