参照附录 1,了解 Socket 通信的原理,在此基础上编写一个聊天程序。
附录 1、Windows Socket 编程简介 使用 WinSock API 的编程,应该了解 TCP/IP 的基础知识。虽然你可以直接使用 WinSock API 来写 网络应用程序,但是,要写出优秀的网络应用程序,还是必须对 TCP/IP 协议有一些了解的。
1. TCP/IP 协议与 WinSock 网络编程接口的关系 WinSock 并不是一种网络协议,它只是一个网络编程接口,也就是说,它不是协议,但是它可以 访问很多种网络协议,你可以把它当作一些协议的封装。现在的 WinSock 已经基本上实现了与协议无 关。你可以使用 WinSock 来调用多种协议的功能。那么,WinSock 和 TCP/IP 协议到底是什么关系呢? 实际上,WinSock 就是 TCP/IP 协议的一种封装,你可以通过调用 WinSock 的接口函数来调用 TCP/IP 的各种功能.例如我想用 TCP/IP 协议发送数据,你就可以使用 WinSock 的接口函数 Send()来调用 TCP/IP 的发送数据功能,至于具体怎么发送数据,WinSock 已经帮你封装好了这种功能。
......
可以参考教材计算机网络(第 6 版)295 页图 6-32 所示的系统调用使用顺序:
服务器端编程的步骤:
1:加载套接字库,创建套接字(WSAStartup()/socket());
2:绑定套接字到一个 IP 地址和一个端口上(bind());
3:将套接字设置为监听模式等待连接请求(listen());
4:请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());
5:用返回的套接字和客户端进行通信(send()/recv());
6:返回,等待另一连接请求;
7:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
如下代码在Ubuntu中运行,与Windows相比,调用了不同的库:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define BUF_SIZE 100
int main()
{
//创建套接字
//AF_INET 表示使用 IPv4 地址,SOCK_STREAM 表示使用面向连接的数据传输方式,IPPROTO_TCP 表示使用 TCP 协议
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int pid;
//将套接字和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"); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //将套接字 serv_sock 与特定的IP地址和端口绑定,IP地址和端口都保存在 sockaddr_in 结构体中
//进入监听状态,等待用户发起请求
listen(serv_sock, 20); //收到connect()发来的消息被激活
//接收客户端请求
struct sockaddr_in clnt_addr;
socklen_t clnt_addr_size = sizeof(clnt_addr);
char buffer[BUF_SIZE] = {0}; //缓冲区
if((pid=fork())<0)
{
perror("fork error\n");
}
else if(pid>0)/*child*/
{
while(1)
{
int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); //阻塞(暂停运行)直到有请求到达,继续执行
recv(clnt_sock, buffer, BUF_SIZE, 0); //接收客户端发来的数据
printf("Message form client: %s\n", buffer);
//send(clnt_sock, buffer, strLen, 0); //将数据原样返回
close(clnt_sock); //关闭套接字
memset(buffer, 0, strlen(buffer)); //重置缓冲区
}
}
else
{
while(1)
{
int clnt_sock_1 = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); //阻塞(暂停运行)直到有请求到达,继续执行
scanf("%s",buffer);
send(clnt_sock_1, buffer, strlen(buffer), 0);
close(clnt_sock_1); //关闭套接字
memset(buffer, 0, strlen(buffer)); //重置缓冲区
}
}
close(serv_sock);
return 0;
}
客户端编程的步骤:
1:加载套接字库,创建套接字(WSAStartup()/socket());
2:向服务器发出连接请求(connect());
3:和服务器端进行通信(send()/recv());
4:关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup())。
如下代码在Ubuntu中运行,与Windows相比,调用了不同的库:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 100
int main(){
//向服务器(特定的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"); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口
int pid;
char bufSend[BUF_SIZE] = {0};
char bufRecv[BUF_SIZE] = {0};
if((pid=fork())<0)
{
perror("fork error\n");
}
else if(pid>0)/*child*/
{
while(1){
//创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //触发三次握手
//获取用户输入的字符串并发送给服务器
printf("Input a string: ");
fgets(bufSend, BUF_SIZE, stdin);
send(sock, bufSend, strlen(bufSend), 0);
memset(bufSend, 0, BUF_SIZE); //重置缓冲区
//memset(bufRecv, 0, BUF_SIZE); //重置缓冲区
close(sock); //关闭套接字
}
}
else
{
while(1)
{
//创建套接字
int sock_1 = socket(AF_INET, SOCK_STREAM, 0);
connect(sock_1, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //触发三次握手
//接收服务器传回的数据
recv(sock_1, bufRecv, BUF_SIZE, 0);
//输出接收到的数据
printf("Message form server: %s\n", bufRecv);
memset(bufRecv, 0, BUF_SIZE); //重置缓冲区
close(sock_1); //关闭套接字
}
}
return 0;
}