socket简介
在计算机领域,socket是计算机之间进行通信的一种约定或者方式,通过socket这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。
1. IP地址
本机IP地址是127.0.0.1,当需要通信时,需要将IP地址封装到要发送的数据包中,路由器可以找到目标计算机,并将数据包传递给它,完成一次单向通信。
2. 端口(Port)
一台计算机可以同时提供多种网络服务,Web服务,FTP服务,SMTP服务(邮箱),仅有IP地址,计算机可以正常接收到数据包,但是不知道选取那个网络程序来处理。为了区分不同的网络程序,计算机会为每个网络程序分配一个独一无二的端口号。
端口(Port)是一个虚拟的、逻辑上的概念。可以将端口理解为一道门,数据通过这道门流入流出,每道门有不同的编号,就是端口号。
3. 协议
协议是网络通信的约定,通信双方必须都遵守才能正常收发数据。协议有很多种,例如TCP、UDP、IP等。
协议族是一组协议的统称。最常用的是TCP/IP协议族,它包含了TCP、IP、UDP、Telnet、FTP、SMTP等。由于TCP、IP是常用的底层协议,所以把他们统称TCP/IP协议族。
4. 数据传输方式
计算机之间有很多数据传输方式,常用的有SOCK_STREAM和SOCK_DGRAM.
(1) SOCK_STREAM表示面向连接的数据方式。数据可以准确无误的到达另一台计算机。如果出现损坏或者丢失,将会重新发送,因此效率较低。常见的HTTP协议就是使用SOCK_STREAM传输数据。
(2)SOCK_DGRAM表示无连接的数据传输方式。计算机只管传输数据,不做数据校验。如果传输中损坏或者丢失,无法找回,SOCK_STREAM不会频繁的丢失数据。因此效率较高。
有可能多种协议使用同一种数据传输方式,所以在socket编程中需要同时指明数据传输方式和协议。
Linux socket编程演示
需要实现的功能:客户端clien.cpp从服务器端server.cpp读取一个字符串并打印出来。
server.cpp:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet.h>
int main(){
//通过socket()函数创建套接字
int serv_sock = socket(AF_INET,//表示使用IPv4地址
SOCK_STREAM, //表示面向连接的数据传输方式
IPPROTO_TCP);//使用TCP协议
//将套接字与IP、端口绑定
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));//每个字节用0填充
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//ip地址
serv_addr.sin_port = htons(1234);//端口
//将套接字serv_sock与特定的IP地址和端口绑定,IP地址和端口都保存在sockaddr_in结构体中
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
/*进入被动监听状态,等待用户发起请求。所谓被动监听,
是指套接字一直处于“睡眠”中,直到客户端发起请求才会被“唤醒”*/
listen(serv_sock, 20);
//接收客户端请求
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
//程序一旦执行到accept就会被堵塞(暂停运行),直到客户端发起请求
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, $clnt_addr_size);
//向客户端发送数据
char str[] = "Hello World";
//向套接字写入数据
write(clnt_sock, str, sizeof(str));
//关闭套接字
close(clnt_sock);
close(serv_sock);
return 0;
}
client.cpp:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
int main(){
//创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
//向服务器发起请求(特定的IP和端口)
struct sockaddr_in serv_addr;
memset($serv_addr, 0, sizeof(serv_addr));//用0填充每个字节
serv_addr.sin_family = AF_INET;//使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(1234);
connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//读取服务器传回的数据
char buffer[40];
read(sock, buffer, sizeof(buffer)-1);
printf("Message from server: %s\n", buffer);
//关闭套接字
close(sock);
return 0;
}
先编译server.cpp运行,正常情况下,程序运行到accept()处就会堵塞,处于监听状态,等待客户端发送请求。
接下来运行client.cpp, 通过connect()函数向server发送请求,处于监听状态的server被激活,执行accept()函数,接收客户端发送的请求,然后执行write()函数向client传回数据。client接收到传回的数据后,connect就结束了,然后在利用read()将数据读出来。
需要注意的是:
(1)本程序server只接收一次client请求,当server向client传回数据后,程序就停止了。如果想再次收到服务器的数据,必须再次运行server.
(2)server通过connect向服务器发送请求,服务器的IP地址和端口号保存在sockaddr_in结构体中。直到服务器传回数据后,connect()才结束运行。
windows socket程序演示
windows下socket程序和Linux思路相同,但细节有所差别:
(1) Windows 下的 socket 程序依赖 Winsock.dll 或 ws2_32.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()函数。
server.cpp:
#include <stdio.h>
#include <winsock2.h>
#pragma comment (lib, "ws2_32.lib");//加载ws2_32.dll
int main(){
//初始化dll
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), $wsaData);
//创建套接字
SOCKET servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
//绑定套接字
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
sockAddr.sin_family = PF_INET; //使用IPv4地址
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //具体的IP地址
sockAddr.sin_port = htons(1234); //端口
bind(servSock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
//进入监听状态
listen(servSock, 20);
//接收客户端请求
SOCKADDR clntAddr;
int nSize = sizeof(SOCKADDR);
SOCKET clntSock = accept(servSock, (SOCKADDR*)&clntAddr, &nSize);
//向客户端发送数据
char *str = "Hello World!";
send(clntSock, str, strlen(str)+sizeof(char), NULL);
//关闭套接字
closesocket(clntSock);
closesocket(servSock);
//终止 DLL 的使用
WSACleanup();
return 0;
}
client.cpp:
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib") //加载 ws2_32.dll
int main(){
//初始化dll
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
//创建套接字
SOCKET sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
//向服务器发起请求
sockaddr_in sockAddr;
memset(&sockAddr, 0, sizeof(sockAddr)); //每个字节都用0填充
sockAddr.sin_family = PF_INET;
sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
sockAddr.sin_port = htons(1234);
connect(sock, (SOCKADDR*)&sockAddr, sizeof(SOCKADDR));
//接收服务器传回的数据
char szBuffer[MAXBYTE] = {0};
recv(sock, szBuffer, MAXBYTE, NULL);
//输出接收到的数据
printf("Message form server: %s\n", szBuffer);
//关闭套接字
closesocket(sock);
//终止使用 DLL
WSACleanup();
system("pause");
return 0;
}