网上一搜,就可以看到一大堆的Socket编程介绍,一般是写个简单的服务端和客户端进行连接并做简单的交互。我这里又要多一篇这样的文章了,并不是故意的,而是有意的:D.当然不是直接复制粘贴,而是学习了解后亲自实践下来体会体会。
之前做了个五子棋,要可以两个人对战的。如果是单机的话,两个人对战或人机对战,都是在一个客户端上进行的,你只需要考虑在一个客户端上的情况就行了。如果你写了一个客户端,同时安装在两台机器上,并要可以对战交流,那要怎么办?你这边的操作,对面是看不到的,就像两个隔很远的人,没有通讯工具怎么交流。因此这两个客户端也需要一个”通讯工具”,那就是服务端。有了通讯工具,如果两人的语言不一样,也是无法交流的,因此两个客户端之间要用一种双方都懂的规范才能交流,这种规范叫协议,如常见的TCP协议。当然,这两个客户端要能联网,否则怎么连上服务端。服务端负责监听客户端,接收客户端发来的消息,然后给客户端发送消息。举个栗子:例如我这边下子了,然后我给服务端发送一条消息说-我下子了,棋子的颜色是黑色,位置是(x, y),然后服务端接收到消息后就给另一个客户端发条消息说,他下子了,然后另一个客户端就根据提供的消息,在自己棋盘上相应的位置下个黑子。反过来也一样,对方下子,我也会收到消息。这样,两个客户端间就像在同一个棋盘下子一样。服务端并不只是收发消息,像那些网游服务端,还要处理很多逻辑。当然,我做的五子棋并没有服务端,也没有用到Socket编程和什么TCP协议,我只是处理好前端逻辑,然后给别人接上对方提供的SDK,前端可以给那边发消息或接收消息。举的这个栗子不太恰当,不过应该可以帮助理解。
至于什么是Socket,什么是TCP,我这里就不讲了,别人写的很详细,我无法写得更详细,而且这个好像讲一下是讲不完的,我们先简单知道下就可以了。可以看看这篇文章socket编程之实现一个简单的TCP通信。如果你觉得这篇也不详细,那就再看看其它的。或者里边还有不懂的,你可以搜索一下,比如浅谈TCP协议的端口(port),再比如socket开发中INADDR_ANY”的含义是什么?。我觉得看完这3篇就差不多了,可以自己实现代码体验一下。
两台联网的机器如何通信?其实只要用Socket写个简单的服务端和一个简单的客户端分别运行就行了。当然,服务端和客户端的IP地址和端口得一样,不然它们是无法连接上的。
下面来看下代码:
wtlServer.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// 端口,任意指定
#define PORT 9990
// 有多块网卡就会有多个IP地址,可填 (INADDR_ANY) 或填你电脑这在使用的IP地址,格式为 ("xx.xx.xx.xx")
#define IP (INADDR_ANY)
// 用来储存接收消息的缓冲区的大小
#define BUF_SIZE 1024
// 消息处理函数
typedef int(*fn)(int);
// 接收消息的缓冲区
static unsigned char gMsg[BUF_SIZE];
int wtlServer_Init(int* ipSockfd);
void wtlServer_Run(int iSockfd, fn callback);
void wtlServer_Quit(int iSockfd);
static int OnRecvHandle(int iSock);
int main(int argc, char* argv[])
{
// 服务端文件描述符
int iSockfd;
// 运行服务端
wtlServer_Run(wtlServer_Init(&iSockfd), OnRecvHandle);
// 服务端退出
wtlServer_Quit(iSockfd);
return 0;
}
// 服务端初始化函数,用于创建一个服务端的文件描述符,并会返回该描述符
int wtlServer_Init(int* ipSockfd){
int status;
// 服务端文件描述符
int iSockfd;
// 用于存储服务端的一些信息,包括IP地址和端口等
struct sockaddr_in server;
// 创建一个服务端文件描述符,后面操作服务端都是根据该文件描述符进行的
// 第一个参数一般都是这个,第二个参数是套接字类型,SOCK_STREAM代表TCP协议,第三个一般为0
iSockfd = socket(AF_INET, SOCK_STREAM, 0);
// 创建失败时的处理
if (iSockfd < 0) {
perror("socket");
exit(0);
}
// 填写服务端的相关信息
server.sin_family = AF_INET;
// 端口。htons()函数在上面给的连接有介绍,还有个htonl(),注意使用
server.sin_port = htons(PORT);
// IP地址。注意,如果服务端和客户端都跑在同一台机器,就用下面这句
server.sin_addr.s_addr = htons(INADDR_ANY);
// 如果跑在不同机器,就用下面这句。IP为服务端运行的机器的IP地址
//server.sin_addr.s_addr = inet_addr(IP);
// 将文件描述符和服务端信息进行绑定
status = bind(iSockfd, (struct sockaddr*)&server, sizeof(server));
// 绑定失败时的处理
if (status < 0) {
perror("bind");
exit(0);
}
// 监听是否有客户端请求连接,如果有,将其放到等待队列
status = listen(iSockfd, 5);
// 监听失败时的处理
if (status < 0) {
perror("listen");
exit(0);
}
*ipSockfd = iSockfd;
return iSockfd;
}
// 运行服务端。第一个参数为服务端的文件描述符,第二个参数为收到客户端发来消息后的回调函数
void wtlServer_Run(int iSockfd, fn callback){
// 该变量用来存放已连接上的客户端的相关信息,如IP地址和端口
struct sockaddr_in client;
// 已连接客户端的文件描述符
int iSock;
int status = 0;
int len = sizeof(client);
while (1) {
// 客户端服务端进行3次握手后,就可以正确连接
// 函数的第一个参数为服务端的文件描述符,第二个参数用来填写连接的客户端信息
// 成功连接后,该函数返回客户端的文件描述符
iSock = accept(iSockfd, (struct sockaddr*)&client, &len);
// 连接失败时的处理
if (iSock < 0) {
fprintf(stdout, "Waiting for a client to conect...");
continue;
} else {
// 连接成功,打印客户端的IP地址和端口号
fprintf(stdout, "Conecting a client: IP-%s PORT:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
}
if (callback != NULL) {
// 收到客户端发来消息后的处理
status = callback(iSock);
}
// 如果处理结果得到-1,说明客户端要退出了
if (status == -1) {
fprintf(stdout, "Client Disconected...\n");
break;
}
}
}
// 服务端退出后,关闭服务端文件描述符
void wtlServer_Quit(int iSockfd){
close(iSockfd);
}
// 收到客户端发来消息后的处理函数,参数为客户端的文件描述符
int OnRecvHandle(int iSock){
ssize_t size;
while (1) {
// 从客户端文件描述符中读取这么多个字节到gMsg缓冲中
size = read(iSock, gMsg, sizeof(gMsg) - 1);
if (size > 0) {
gMsg[size] = 0;
// 读取到消息后,服务端只是简单的打印出来
printf("%s\n", gMsg);
// 如果客户端发来一个 bye ,表示要退出了
if (strncmp(gMsg, "bye", 3) == 0) {
return -1;
}
}
}
return 0;
}
wtlClient.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// 端口得和服务端设置的一致,不然服务端监听不到请求
#define PORT 9990
// IP地址,也得和服务端设置的一致,要么是 INADDR_ANY 要么是具体的某个IP地址
#define IP (INADDR_ANY)
#define SIZE 1024
static unsigned char gMsg[SIZE];
int wtlClient_Init(int* ipSockfd);
void wtlClient_Run(int iSockfd);
void wtlClient_Quit(int iSockfd);
int main(int argc, char* argv[])
{
// 客户端文件描述符
int iSock;
wtlClient_Init(&iSock);
wtlClient_Run(iSock);
wtlClient_Quit(iSock);
return 0;
}
// 初始化客户端,和服务端的初始化差不多
int wtlClient_Init(int* ipSockfd){
int iSock;
int len;
// 用来存放服务端相关信息,如IP地址和端口号
struct sockaddr_in server;
// 创建客户端文件描述符
iSock = socket(AF_INET, SOCK_STREAM, 0);
if (iSock < 0) {
perror("socket");
exit(0);
}
// 填写服务端相关信息,因为要连接到服务端
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = inet_addr(IP);
len = sizeof(server);
// 向服务端请求连接
if (connect(iSock, (struct sockaddr*)&server, len) < 0) {
perror("connect");
exit(0);
} else {
// 连接成功
fprintf(stdout, "Connected to a server!\n");
}
*ipSockfd = iSock;
return iSock;
}
// 运行客户端,参数为客户端文件描述符
void wtlClient_Run(int iSockfd){
int size = 0;
while (1) {
// 从终端输入文字
printf("Client>> ");
fflush(stdout);
// 读取输入的文字到gMsg
size = read(0, gMsg, SIZE - 1);
gMsg[size] = 0;
// 将gMsg中的信息写入到客户端文件描述符中
write(iSockfd, gMsg, size);
if (size > 0) {
// 如果输入 bye ,退出客户端
if (strncmp(gMsg, "bye", 3) == 0) {
fprintf(stdout, "Disconnected to server...\n");
break;
}
}
}
}
// 客户端退出时关闭客户端文件描述符
void wtlClient_Quit(int iSockfd){
close(iSockfd);
}
如果将IP地址填你电脑的IP地址,电脑编译运行wtlSercer.c
,手机装个C4Droid,运行wtlClient.c
,成功连接后,手机发送消息,电脑就可以收到消息了。
这只是简单演示。还可继续改进。 : )