Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求。一个Socket由:IP地址+端口号 所组成;
网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
现在编写两个程序(运行在同一台主机上),一个作为服务器端,一个作为客户端;思路如下:
以下是Windows下的Socket编程,需要的头文件有:
#include<stdio.h>
#include<memory.h>
#include<WinSock2.h> //Socket所在的头文件
但是仅仅包含头文件还不可以,需要把链接文件包含进来:
#pragma comment (lib, "ws2_32.lib")
否则程序无法运行!
服务器端:
0.定义两个socket标识:int listenFd,connectFd;一个用于监听是否有客户进行连接,一个用于和客户端进行连接
1.添加以下一个变量以及一个函数:
WSADATA wsaData;
WSAStartup(0x0101, &wsaData);
WSAStartup函数是为了通知系统调用Socket的Dll(动态链接库文件),故
WSAStartup应先于其他的winsock函数
如果不添加该函数,则容易导致程序无法运行;
2.初始化监听套接字:
int listenFd=socket(AF_INET,SOCK_STREAM,0);
套接字实际是一个int整型,计算机中很多资源、程序都会用一个int进行表示,例如句柄、进程PID、Linux下的文件表示等(有不懂可以自行Google查阅)
#func解说:int sock=socket( protofamily, type, protocol );
protofamily指定所使用的协议族,对TCP/IP协议族取值为AF_INET。type指定采用的 通信类型:流传输模式取值SOCK-STREAM;报文传输模式取值SOCK-DGRAM。protocol指 定所使用的传输协议,可默认为0;
3.定义服务器的地址用于通信:struct sockaddr_in serverAddr;
sockaddr_in是一个结构体:
struct sockaddr_in {
u_char sin_len; /* total length of the address */
u_char sin_family; /* family of the address */
char sin_port; /* protocol port number */
struct in_addr sin_addr; /* IPv4 address of computer *, 通常为INADDR_ANY/
char sin_zero[8]; /* not used (set to zero) */
};
而其中的in_addr也是一个结构体:如下
struct in_addr{
union{ struct{ u_char s_b1, s_b2, s_b3, s_b4;} s_un_b;
struct{u_short s_w1,s_w2;} s_un_w;
u_long s_addr;
} s_un
如果看不懂可以暂时忽略,只要记得结构体sockaddr_in用来储存一个服务器或客户端的地址
4.服务器地址serverAddr的初始化:
a.memset初始化为0:memset(&serverAddr,0,sizeof(serverAddr));
b.指定serverAddr的协议族:sin_family赋值为AF_INET(表示TCP/IP);
c.指定serverAddr的源地址:sin_addr.s_addr=htonl(INADDR_ANY);IP地址设置为系统自动获取本机IP地址
d.指定serverAddr的端口:sin_port=htons(PORT);PORT=自定义端口
5.将服务器地址绑定到监听套接字上:
bind(listenFd,(struct sockaddr *)&serverAddr,sizeof(serverAddr);
只有进行绑定服务器才会拥有一个地址,客户端才可以连接到服务器
#func解说:bind ( socket, addr, addrlen )
socket是所用套接字的描述符;addr是一个结构,它指定将要赋给套接字的地址;addrlen指出地址的长度。
6.开始监听是否有客户端进行连接:
listen(listenFd,10);
#func解说:listen ( socket, queuesize )
socket是所用套接字的描述符,queuesize指定请求队列的长度。
7.在一个死循环中,如果有客户端连接,则连接套接字进行连接
connectFd=accept(listenFd,(struct sockaddr *)NULL,NULL);
#func解说: accept ( socket, caddress, caddresslen )
socket是所用套接字的描述符;caddress是sockaddr_in结构类型的地址,填入已建立连接的客户地址,caddresslen是指向一个整数的指针,指定地址长度。accept为该连接创建一个新的套接字,并将这个新套接字的描述符返回给调用者。
8.接受客户端传过来的数据:
int n=recv(connectFd,buff,MAXLINE,0);
#func解说:recv ( socket, buffer, length, flags )
socket是所用套接字的描述符, buffer指定用来存放接收数据的内存地址, 1ength指定这个缓冲区的大小,flags允许调用者控制一些细节。
9.向客户端发送回应数据:
send(conncetFd,MESSAGE,Len,0);
#func解说:send ( socket, data, length, flags )
socket是所用套接字的描述符,data是待发送数据在内存中的地址,1ength是一个整数,指定数据的字节数,参数flags包含请求特殊选项的比特位。
10.关闭连接
closesocket(connectFd);
closesocket(listenFd);
#func解说:close (socket)
socket是所用套接字的描述符,需要先关闭连接套接字,再关闭监听套接字
完整的代码如下:其中的检测错误下面会进行说明:
#include<stdio.h>
#include<memory.h>
#include<WinSock2.h>
#pragma comment (lib, "ws2_32.lib")
#define MPORT 8000
#define MAXLEN 2048
void main(){
char buff[MAXLEN];
int listenFd, conncetFd;
sockaddr_in serverAddr;
WSADATA wsaData;
WSAStartup(0x0101, &wsaData);
//初始化Socket
listenFd = socket(AF_INET, SOCK_STREAM, 0);
if (listenFd < 0){
printf("Error at socket():%ld\n", WSAGetLastError());
exit(0);
}
//初始化
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(MPORT);//设置端口
serverAddr.sin_addr.s_addr =INADDR_ANY;//IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址
//将本地地址绑定到所创建的套接字上
if (bind(listenFd, (struct sockaddr *)&serverAddr, sizeof(sockaddr)) < 0){
printf("Error at bind():%ld\n", WSAGetLastError());
exit(0);
}
//开始监听是否有客户端连接
if (listen(listenFd, 10) < 0){
printf("Error at listen():%ld\n", WSAGetLastError());
exit(0);
}
printf("=======wait for client=========\n");
while (1){
//阻塞直到有客户端连接,不然多浪费CPU资源
if ((conncetFd = accept(listenFd, (struct sockaddr *)NULL, NULL)) < 0){
printf("Error at accept():%ld\n", WSAGetLastError());
continue;
}
//接受客户端传过来的数据
int n = recv(conncetFd, buff, MAXLEN, 0);
if(n>=0) buff[n] = '\0';
printf("%s\n", buff);
//向客户端发送回应数据
send(conncetFd, "Hello,This message is frome Server\n", 8, 0);
closesocket(conncetFd);
}
closesocket(listenFd);
exit(0);
}
关于以上的异常判断,每执行一个函数,socket()、bind()、listen()等都会返回一个int整型,如果函数运行出错,则返回-1,成功则会返回一个不同的整数(具体返回的整数可以看上面的#func函数解说);
所以,当函数返回-1的时候,调用该函数WSAGetLastError()可以打印出错信息(一个整型),你可以通过查找该整型得到解决方法
客户端:与服务器端比较类似,如下:
0.定义一个socket标记:int connectFd;
1.添加以下一个变量以及一个函数:WSAStartup应先于其他的winsock函数
WSADATA wsaData;
WSAStartup(0x0101, &wsaData)
2.初始化监听套接字:
connectFd=socket(AF_INET,SOCK_STREAM,0);
#func解说:与服务器端相同
3.定义服务器的地址用于通信:struct sockaddr_in serverAddr;
4.服务器地址serverAddr的初始化:
a.memset初始化为0:memset(&serverAddr,0,sizeof(serverAddr));
b.指定serverAddr的协议族:sin_family赋值为AF_INET(TCP/IP);
c.指定serverAddr的源地址:sin_addr.s_addr=inet_addr("127.0.0.1");IP地址设置为本机IP地址
d.指定serverAddr的端口:sin_port=htons(PORT);PORT=自定义端口
5.连接服务器:
connect(connectFd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
#func解说:与服务器端相同
6.如果连接服务器成功,则可以发送数据:
sendLen = send(connect_sockFd, mess, strlen(mess), 0)
#func解说:返回发送的数据长度(sendLen),与服务器端相同
7.接收数据:
recLen = recv(connectFd, recMessage, MAXLEN, 0)
#func解说:与服务器端相同
8.关闭连接
closesocket(connectFd);
完整代码如下:
#include<stdio.h>
#include<WinSock2.h>
#include<memory.h>
#pragma comment (lib, "ws2_32.lib")
#define MPORT 8000
#define MAXLEN 2048
void main(){
int connectFd;
char rec[MAXLEN];
int len=0;
struct sockaddr_in serverAddr;
WSADATA wsaData;
WSAStartup(0x0101, &wsaData);
if ((connectFd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("Error at socket():%ld\n", WSAGetLastError());
exit(0);
}
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
serverAddr.sin_port = htons(MPORT);
if ((connect(connectFd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) )< 0){
printf("Error at connect():%ld\n", WSAGetLastError());
exit(0);
}
printf("conncect succeed\n");
int temp;
if ((temp = send(connectFd, "Hello world", strlen("Hello world"), 0)) < 0){
printf("send error\n");
}
if ((len = recv(connectFd, rec, MAXLEN, 0) )< 0){
printf("Error at recv():%ld\n", WSAGetLastError());
exit(0);
}
rec[len] = '\0';
printf("%s\n", rec);
closesocket(connectFd);
}