vc6.0怎么创建一个服务器:先搞懂底层逻辑
很多朋友一上来就翻MSDN,结果被一堆结构体吓退了,其实在VC6.0里做服务器,思路和现实中开一家店一模一样:先开业(初始化),再租个门面(绑定端口),然后挂上招牌(监听),最后迎接客人(接受连接)。
这里的关键技术叫SOCKET,中文叫套接字,Windows系统把它封装成了Winsock库,VC6.0里既能用原始的Winsock函数,也能用MFC封装好的CSocket类,如果你是刚学网络编程,我建议先用最简单的API函数把流程跑通,再去折腾CSocket,要不然后期排查问题会绕很多弯路。
行业共识认为,学习Winsock编程时,把一个TCP服务器拆成初始化、创建、绑定、监听、接受、收发六个动作是最清晰的理解方式,下面我们拆开来看。
第一件必须做的事:初始化Winsock库
VC6.0的编译器比较老,它默认不会自动加载Winsock库,所以第一步要手动初始化,这步漏了,后面的socket函数全都会返回一个奇怪的错误码。
初始化代码很固定,就两行核心逻辑:
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
MAKEWORD(2,2)表示请求Winsock 2.2版本,注意,把第一个参数填成MAKEWORD(1,1)也行,但现在基本不用了,初始化成功后会返回0,你可以加个判断,如果不等于0就弹出错误提示。
创建Socket:服务器也先要有个”身份证”
初始化搞定后,用socket函数创建套接字描述符,代码长这样:
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
三个参数分别是地址族(IPv4)、套接字类型(流式)、协议(自动匹配为TCP),如果你看到代码里用的参数是AF_INET和SOCK_DGRAM,那是UDP协议,做的是无连接通信,和咱们说的服务器不是一回事。
绑定端口:把应用程序和系统网络地址拴在一起
绑定的过程就是填充sockaddr_in结构体,然后调用bind函数,这里面端口号至少要设置两个信息,一个是端口,另一个是允许所有网卡接受连接。
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(8888);
bind(serverSocket, (sockaddr)&serverAddr, sizeof(serverAddr));
端口为什么是8888?其实哪都行,别用80、443这种容易被占用的,局域网内自己测试建议直接选10000以上,避免冲突。
监听与接受:进入”营业状态”
监听就是告诉系统开始接收连接请求,这一步用listen函数,注意backlog参数,表示排队等待连接的数量,填5就够用了。
然后你需要用一个循环来调用accept,这个函数是阻塞的,也就是说没有客户端连进来,程序会一直停在这里等,等到了客户端,它会返回一个新的套接字描述符,这个新套接字专门负责和那个客户端通信,原来的serverSocket继续听别的连接请求。
vc6.0 socket编程教程:服务器端代码核心步骤拆解
下面给你一份可以直接抄的完整代码,这段代码实现的功能是:服务器启动后监听8888端口,客户端连进来后,服务器给它发送一句欢迎文字,然后接受客户端的一句话并打印,最后关闭连接。
#include <winsock2.h>
#include <stdio.h>
void main(){WSADATA wsaData;WSAStartup(MAKEWORD(2,2), &wsaData);
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(8888);
bind(serverSocket, (sockaddr)&serverAddr, sizeof(serverAddr));
listen(serverSocket, 5);
printf("服务器已启动,等待客户端连接...\n");
sockaddr_in clientAddr;
int len = sizeof(clientAddr);
SOCKET clientSocket = accept(serverSocket, (sockaddr)&clientAddr, &len);
char msg[] = "欢迎连接到VC6.0服务器";
send(clientSocket, msg, sizeof(msg), 0);
char recvBuf[256];
recv(clientSocket, recvBuf, 256, 0);
printf("收到客户端消息: %s\n", recvBuf);
closesocket(clientSocket);
closesocket(serverSocket);
WSACleanup();
多客户端连接:别让服务器一次只服务一个人
上面这份代码是一次性的,一个客户端断开后服务器就结束了,正常的服务器应该像一家餐厅,不停地接客,你要做的是把accept、recv、send这段逻辑放进while循环里,或者用多线程为每个连接开一个独立线程。
在VC6.0里开多线程,用CreateThread函数就行,记住每个线程里自己创建套接字副本,不要多个线程共享同一个SOCKET变量,否则会出现数据串包的问题,业内专家指出,多线程socket编程80%的崩溃问题都出在共享变量没加锁。
vc6.0网络编程入门:你多半会碰到的三个坑
第一道坎,是链接时报错,代码写得再工整,如果你忘了告诉VC6.0要链接Winsock库,一编译就会出现一堆unresolved external symbol的错误,解决办法是在工程设置里加ws2_32.lib,或者直接在代码最上面写一句预处理指令:
#pragma comment(lib, "ws2_32.lib")
第二道坎,是防火墙拦截,你自己电脑当服务器,Windows防火墙会默认拦截外部设备访问,测试的时候可以临时关掉防火墙,或者直接在防火墙放行规则里把exe加进去,注意是放行你编译出来的那个exe程序,不是放行整个工程。
第三道坎,是地址被占用,如果你的程序异常退出,端口不会立刻释放,需要等一分钟左右,这也是为什么排错时经常遇到bind失败的原因,解决办法是使用SO_REUSEADDR选项,在bind前调用setsockopt。
一个表格说清VC6.0和Visual Studio新版做Socket的区别
对比项
VC6.0
新版VS
默认字符集
多字节(MBCS)
Unicode(UTF-16)
Winsock头文件
winsock2.h
winsock2.h
数据结构
sockaddr_in
sockaddr_in(兼容)
排除预编译问题
常见
不常见
Debug调试体验
较差
更好
很多人在网上找vc6.0与visual studio哪个好的答案,其实在Socket编程层面,两者差别不大,核心API和结构体全部一致,最大的差别在于调试工具和语法提示,新版VS对代码补全和错误定位更友好,但VC6.0轻巧、启动快,老工程维护仍有大量使用者。
把服务器程序扩展成实际应用
很多教程到上面就结束了,但我想多提一句,真实项目里服务器不可能只收发一轮消息就关门,你得考虑健壮性,比如接收数据时,对方一次性发过来的内容可能超过缓冲区大小,所以recv的返回值必须检查,要循环接收直到收满指定长度。
粘包和拆包是网络编程另一个绕不开的话题,TCP是流式协议,没有消息边界,你发的两条消息可能会黏在一起到达对端,也可能会被拆成两次送达,解决办法是在数据前面加一个固定长度的包头,里面记录消息体的长度,接收方先收包头,再按长度收包体,这是目前最通用也最稳定的方案。
加一个简单通信协议让代码更规范
不用搞太复杂,定义成下面这样:
struct MyMsg {
int len;
char data[1024];
};
send的时候先发送sizeof(int)个字节,再发送data,recv端先用recv收4个字节拿到长度,再循环收够len个字节,别嫌麻烦,这能帮你避开80%以上的网络编程隐形雷区。
一个能跑通的完整示例:局域网文件传输服务器雏形
检验你学得怎么样,做个实用的小工具是最好的验证,我建议你做一个文件接收服务器:客户端发文件名和文件内容,服务器保存到本地,这个项目用上面所有知识点就能做出来。
关键点是按二进制文件处理,不要用文本模式收发,先把文件名长度发过去,再发文件名,接着发文件大小,最后循环发送文件内容,完整代码篇幅比较长,核心骨架和上面的TCP服务器完全一致,就是收发内容的逻辑换成读写硬盘文件而已。
近年来,这类局域网聊天程序开发代码在网上的热度一直很高,很多实训课和毕业设计还在用这台老编译器,它的好处是代码简洁、依赖少,适合用来理解网络原理。
如果要长期使用VC6.0做服务器开发,记住这三条建议
- 参数检查不能省:socket、bind、listen、accept这几个函数返回值都必须检查,任何一个为SOCKET_ERROR都说明前面有环节出错。
- 多打印调试信息:printf到控制台是最直观的排错方式,VC6.0下别用cout,遇到中文字符集容易出乱码。
- 善用社区资源:CSDN和Stack Overflow上有大量VC6.0老代码,遇到问题先搜索再动手。
最后再总结一句:VC6.0创建服务器的步骤不会因为编译器老而改变,核心就是初始化、建Socket、绑定端口、监听、接受连接这五件事,你只需要记住这个流程,把你需要的业务逻辑填充进去,一个可用的服务器就能跑起来。
关于VC6.0服务器创建的三个高频疑问
FTP或者HTTP服务器也能用VC6.0实现吗
能,HTTP协议底层就是TCP,你只需要在accept之后按HTTP协议格式去解析请求头,再返回响应内容,解析请求行、Header、Body,这段逻辑手动写起来稍长,但几百行内能实现一个静态文件服务器。
局域网里其他电脑连不上我的VC6.0写的服务端,怎么排查
先确认客户端和服务器在同一个网段,然后ping一下服务器IP看通不通,通则下一步检查端口监听是否成功,命令行用netstat -an | findstr 8888查看监听状态,若监听正常,检查防火墙入站规则,据工信部网络安全统计,过半数的局域网连接失败问题都出在Windows防火墙默认拦截上。
VC6.0写服务器有必要学MFC的CSocket吗
如果你只是做练习,可以直接用SOCKET API,透明度更高,若要做界面程序,CSocket可以配合对话框消息机制使用,但封装级别较高,调试底层问题时会比较费神,多数老项目的服务器核心仍然用API实现,界面用CSocket做客户端。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/791786.html


