socket编程
一、sockket定义
在计算机通信领域,socket翻译为:“套接字”。他是计算机之间通讯的一种的方式。IP地址和端口能够在广袤的互联网中定位到要通信的程序,协议和数据传输方式规定了如何传输数据,有了这些,两台计算机就可以通信了。
二、实现通信的条件
两台计算机之间要通信必须知道对方的IP地址和端口号
2.1IP地址(IP Address)
计算机分布在全世界,彼此要通信,必须知道对方的确切的位置,通常是通过确定IP地址来进行通信的,我们计算机不知道IP地址对应的地理位置,通常是将IP地址封装到发送的数据包中,交给路由器处理,路由器通过算法,找到目标计算机,并将数据包传给他,完成一次通信。
目前主要有IPV4和IPV6两种,因IPV4资源已经耗尽,现阶段在推广IPV6
2.2端口(Port)
有了IP地址,虽然可以找到目标机,但是任然不能进行通信,因为通常一台计算机同时提供多种网路服务,通常有:FTP、SMTP、Web等服务,仅有IP地址,计算机可以接受到数据,但是不知道将数据包交给哪个网络进行处理,因此还不能正常通信。
计算机为了区分各种网络程序,会为每一个程序分配独一无二的端口号,例如:网络服务:80端口,FTP:21 SMTP:25。端口是虚拟的,可以将端口理解为门户,数据需要通过门户流进流出,每一个门户都有一个门牌号,就是端口号。
三、通信协议及数据传输方式
3.1 通信协议
协议(Protocol)就是网络通信的约定,通信的双方必须都遵守才能正常收发数据。协议有很多种,例如 TCP、UDP、IP 等,通信的双方必须使用同一协议才能通信。协议是一种规范,由计算机组织制定,规定了很多细节,例如,如何建立连接,如何相互识别等。
所谓协议族(Protocol Family),就是一组协议(多个协议)的统称。最常用的是 TCP/IP 协议族,它包含了 TCP、IP、UDP、Telnet、FTP、SMTP 等上百个互为关联的协议,由于 TCP、IP 是两种常用的底层协议,所以把它们统称为 TCP/IP 协议族。
2.2数据传输方式
计算机之间有很多数据传输方式,各有优缺点,常用的有两种:SOCK_STREAM 和 SOCK_DGRAM。
1) SOCK_STREAM 表示面向连接的数据传输方式。数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送,但效率相对较慢。常见的 http 协议就使用 SOCK_STREAM 传输数据,因为要确保数据的正确性,否则网页不能正常解析。
2) SOCK_DGRAM 表示无连接的数据传输方式。计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。因为 SOCK_DGRAM 所做的校验工作少,所以效率比 SOCK_STREAM 高。
QQ 视频聊天和语音聊天就使用 SOCK_DGRAM 传输数据,因为首先要保证通信的效率,尽量减小延迟,而数据的正确性是次要的,即使丢失很小的一部分数据,视频和音频也可以正常解析,最多出现噪点或杂音,不会对通信质量有实质的影响。
四、window/Linux编程实例
4.1 windows下Socket编程实例:
4.1.1 server.cpp
1 #include<stdio.h> 2 #include<winsock2.h> 3 #pragma comment(lib,"ws2_32.lib") 4 5 int main() 6 { 7 // 初始化 8 WSAData wsaData; 9 WSAStartup(MAKEWORD(2,2),&wsaData); 10 //创建套接字 11 SOCKET serverSock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); 12 //绑定套接字 13 sockaddr_in sockAddr; 14 memset(&sockAddr, 0, sizeof(sockAddr));//每个字节都填充0 15 sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//具体的IP地址 16 sockAddr.sin_family = PF_INET;//使用IPV4 17 sockAddr.sin_port = htons(1234); 18 bind(serverSock,(SOCKADDR*)&sockAddr,sizeof(SOCKADDR)); 19 20 //监听 21 listen(serverSock,5); 22 //接受客户端请求 23 SOCKADDR clntAddr; 24 int nSize = sizeof(SOCKADDR); 25 SOCKET clntSock = accept(serverSock,(SOCKADDR*)&clntAddr,&nSize); 26 27 //向客户端发送数据 28 char *str = "hello world!"; 29 send(clntSock,str,strlen(str)+sizeof(char),NULL); 30 //关闭套接字 31 closesocket(clntSock); 32 closesocket(serverSock); 33 //终止DLL的使用 34 WSACleanup(); 35 return 0; 36 37 }
4.2.2 client.cpp
1 #include<stdio.h> 2 #include<winsock2.h> 3 #pragma comment(lib,"ws2_32.lib") 4 5 int main() 6 { 7 //初始化 8 WSADATA wsaData; 9 WSAStartup(MAKEWORD(2,2),&wsaData); 10 //创建套接字 11 SOCKET sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); 12 //向服务器发送请求 13 sockaddr_in sockAddr; 14 sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); 15 sockAddr.sin_family = PF_INET; 16 sockAddr.sin_port = htons(1234); 17 connect(sock,(SOCKADDR*)&sockAddr,sizeof(SOCKADDR)); 18 //接受服务器传输的数据 19 char szBuff[MAXBYTE]; 20 recv(sock,szBuff,MAXBYTE,NULL); 21 printf("Message from server:%s\n",szBuff); 22 //关闭套接字 23 closesocket(sock); 24 WSACleanup(); 25 system("pause"); 26 return 0; 27 }
分别编译server.cpp和client.cpp,选运行server.exe,在运行client.exe,其结果为:Message form server: hello world!
4.2 Linux下socket编程
4.2.1 server.cpp
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 #include <arpa/inet.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 9 int main(){ 10 //创建套接字 11 int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 12 13 //将套接字和IP、端口绑定 14 struct sockaddr_in serv_addr; 15 memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充 16 serv_addr.sin_family = AF_INET; //使用IPv4地址 17 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址 18 serv_addr.sin_port = htons(1234); //端口 19 bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 20 21 //进入监听状态,等待用户发起请求 22 listen(serv_sock, 20); 23 24 //接收客户端请求 25 struct sockaddr_in clnt_addr; 26 socklen_t clnt_addr_size = sizeof(clnt_addr); 27 int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); 28 29 //向客户端发送数据 30 char str[] = "Hello World!"; 31 write(clnt_sock, str, sizeof(str)); 32 33 //关闭套接字 34 close(clnt_sock); 35 close(serv_sock); 36 37 return 0; 38 }
4.2.2 client.cpp
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 #include <arpa/inet.h> 6 #include <sys/socket.h> 7 8 int main() { 9 //创建套接字 10 int sock = socket(AF_INET, SOCK_STREAM, 0); 11 12 //向服务器(特定的IP和端口)发起请求 13 struct sockaddr_in serv_addr; 14 memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充 15 serv_addr.sin_family = AF_INET; //使用IPv4地址 16 serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址 17 serv_addr.sin_port = htons(1234); //端口 18 connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 19 20 //读取服务器传回的数据 21 char buffer[40]; 22 read(sock, buffer, sizeof(buffer) - 1); 23 24 printf("Message form server: %s\n", buffer); 25 26 //关闭套接字 27 close(sock); 28 29 return 0; 30 }
先编译运行server.cpp
[admin@lcj ~]$ g++ server.cpp - o server
[admin@lcj ~]$ . / server
在编译client.cpp
[admin@lcj ~]$ g++ client.cpp -o client [admin@lcj ~]$ ./client Message form server: Hello World!
4.3 windows/linux中socket编程差异
1) Windows 下的 socket 程序依赖 Winsock.dll 或 ws2_32.dll,必须提前加载。DLL 有两种加载方式,请查看:动态链接库DLL的加载
2) Linux 使用“文件描述符”的概念,而 Windows 使用“文件句柄”的概念;Linux 不区分 socket 文件和普通文件,而 Windows 区分;Linux 下 socket() 函数的返回值为 int 类型,而 Windows 下为 SOCKET 类型,也就是句柄。
3) Linux 下使用 read() / write() 函数读写,而 Windows 下使用 recv() / send() 函数发送和接收。
4) 关闭 socket 时,Linux 使用 close() 函数,而 Windows 使用 closesocket() 函数。