2019年6月25日
Jerry
1352
2019年6月27日
人生不止眼前的苟且,代码也不止数据的增删改查,也有有趣的网络编程。如何用C语言做一个简单的服务器和客户端,实现一个聊天室程序呢?这里就简单的写一下博主的实现。
一、程序需求
实现一个简单的服务器,包括以下功能:
- 可以监听并且与多个客户端建立TCP链接。
- 可以接收客户端发来的消息,并发送给所有的客户端。
- 可查询所有的客户端链接。
实现客户端有如下功能:
- 输入服务器端口号,与服务器端建立链接。
- 发送消息给服务器。
- 接收并打印服务器发来的消息。
二、实现分析
借用百度的一张图,这个流程很好的解释了socket编程的根本:
服务器端的处理流程思路:
- 创建socket套接字
- 绑定ip与端口号
- 创建监听线程,等待客户端链接
- 接收到一个客户端链接则创建一个线程进行消息接收处理
- 客户端关闭,关闭相关进程
- 服务器关闭
客户端的处理流程思路:
- 创建socket套接字
- 输入端口号链接服务器
- 创建线程处理服务器消息
- 接收控制台输入发送给服务器
- 客户端关闭
socket的基本api:
包含在在头文件“winsock2.h”,官网说明地址: https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/
三、基本的socket API
1、创建一个socket套接字:
/*
domain:协议域、地址域或协议族。常用的协议族有:AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX)、AF_ROUTE等等
type:socket类型。常用的socket类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等
protocol:指定使用的协议。常用的协议有:IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等等
返回值:成功返回一个非负整数的fd,失败则返回-1
*/
int socket(int domain, int type, int protocol);
2、绑定套接字
/*
sockfd:socket文件描述符,即socket()的返回值
addr:指向sock地址的指针
addrlen:sock地址的长度
返回值:成功返回0,失败返回-1
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
3、监听套接字
/*
sockfd:被监听的套接字
backlog:最大等待队列数量
返回值:成功返回0,失败返回-1
*/
int listen(int sockfd, int backlog);
4、连接socket套接字
/*
sockfd:客户端的套接字
addr:服务器的地址
addrlen:服务器的地址的长度
返回值:成功返回0,失败返回-1
*/
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
5、接受socket连接请求
/*
sockfd:被监听的套接字
addr:返回客户端的地址,可为NULL
addrlen:指定客户端的地址的长度,可为NULL
返回值:成功返回已连接的新套接字描述符connfd,失败返回-1
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
6、socket接收数据
/*
sockfd:从这个socket接收数据
buf:用来保存接收到的数据
len:指定buf的长度,表示最多接收这么多个字节的数据
flags:flags,指定对应的选项,一般置为0
返回值:成功返回接收到的数据大小,返回0表示对方不再发送数据(可以理解为关闭了连接),出错返回-1
*/
int recv(int sockfd, void *buf, int len, int flags);
7、socket发送数据
/*
sockfd:向这个socket发送数据
buf:发送buf指向的数据
len:指定buf的长度,指定要发送的数据大小
flags:flags,指定对应的选项,一般置为0
返回值:成功返回已发送的数据大小,失败则返回-1
*/
int send(int sockfd, void *buf, int len, int flags);
8、从udp socket接收数据
/*
sockfd:输入参数,从该socket接收数据
buf:输出参数,将接收的数据存放在buf上
len:输入参数,指定buf的长度
flags:输入参数,flags,指定对应的选项,一般置为0
addr:输出参数,保存该数据的发送方地址
addrlen:输入参数,指定发送方地址的长度
返回值:成功返回接收到的数据大小,失败则返回-1
*/
int recvfrom(int sockfd, void *buf, int len, int flags, struct sockaddr *addr, socklen_t *addrlen);
9、向udp socket发送数据
/*
sockfd:输入参数,向该socket发送数据
buf:输入参数,发送buf指向的数据
len:输入参数,指定buf的长度
flags:输入参数,flags,指定对应的选项,一般置为0
addr:输入参数,指定接收方的地址
addrlen:输入参数,指定接收方的地址的长度
返回值:成功返回发送的数据大小,失败则返回-1
*/
int sendto(int sockfd, void *buf, int len, int flags, struct sockaddr *addr, socklen_t addrlen);
10、关闭socket
/*
fd: 要关闭的socketID
*/
int close(int fd);
四、最终实现效果
服务器端:
客户端:
1、建立多个链接
2、发送消息
3、客户端断开链接
4、服务器关闭
五、代码实现
服务器端:
// server.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#define MAX_NUM 10
typedef struct _client
{
int port;
SOCKET socketid;
HANDLE rev_handle;
}CLIENT_S;
//全局变量区
HANDLE acpt_handle;
CLIENT_S g_client[MAX_NUM];
int g_num = 0;
void init_client()
{
int i;
for(i=0; i < MAX_NUM; i++)
{
memset(&g_client[i], 0, sizeof(CLIENT_S));
}
g_num = 0;
}
void log_print(char *str)
{
printf("[系统日志]:%s\n", str);
}
void message_print(char *str, int port)
{
printf("[用户%d]:%s\n", port, str);
}
void del(int index)
{
int i;
for(i=index; i< g_num; i++)
{
memcpy(&g_client[i], &g_client[i+1], sizeof(CLIENT_S));
}
memset(&g_client[g_num-1], 0, sizeof(CLIENT_S));
g_num--;
}
int getindex_bysocket(SOCKET s)
{
int i;
for(i=0;i<g_num;i++)
{
if(g_client[i].socketid == s)
{
return i;
}
}
return -1;
}
void send_all(char * sendData)
{
int i;
for(i=0;i<g_num;i++)
{
send(g_client[i].socketid, sendData, (int)strlen(sendData), 0);
}
}
int MyRevThread(LPVOID lpParam)
{
SOCKET sClient = SOCKET(lpParam);
char revData[255];
char sendData[255];
int keeplive = true;
while(keeplive)
{
//接收数据
int ret = recv(sClient, revData, 255, 0);
//printf("Myret: %d\n",ret);
if(ret > 0)
{
revData[ret] = 0x00;
int index;
index = getindex_bysocket(sClient);
sprintf(sendData, "[用户%d]: %s", g_client[index].port, revData);
send_all(sendData);
message_print(revData, g_client[index].port);
}
else if(ret == -1)
{
int index;
index = getindex_bysocket(sClient);
char log[50];
sprintf(log, "[用户%d] 断开链接!", g_client[index].port);
log_print(log);
closesocket(g_client[index].socketid);
CloseHandle(g_client[index].rev_handle);
keeplive = false;
del(index);
break;
}
}
return 0;
}
int MyAcpThread(LPVOID lpParam)
{
SOCKET slisten = SOCKET(lpParam);
while(true)
{
SOCKET sClient;
sockaddr_in remoteAddr;
int nAddrlen = sizeof(remoteAddr);
sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
if(sClient == INVALID_SOCKET)
{
log_print("接受到一个连接失败...!");
return -1;
}
DWORD thID=0;
HANDLE handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyRevThread, (LPVOID)sClient, 0, (LPDWORD)&thID);
if(handle == NULL)
{
log_print("消息接收线程创建失败!");
return -1;
}
char log[50];
sprintf(log, "[用户%d] 加入成功! 线程ID %d", remoteAddr.sin_port, thID);
log_print(log);
g_client[g_num].port = remoteAddr.sin_port;
g_client[g_num].rev_handle = handle;
g_client[g_num].socketid = sClient;
g_num++;
}
return 0;
}
void start()
{
//初始化WSA
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(sockVersion, &wsaData)!=0)
{
log_print("初始化WSA失败!");
return;
}
log_print("初始化WSA完成!");
//创建套接字
SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(slisten == INVALID_SOCKET)
{
log_print("创建套接字失败!");
return;
}
log_print("创建套接字完成!");
//绑定IP和端口
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(8888);;
sin.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
log_print("创建套接字失败!");
return;
}
log_print("绑定IP和端口完成!");
//开始监听
if(listen(slisten, 5) == SOCKET_ERROR)
{
log_print("开始监听失败!");
return ;
}
HANDLE handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyAcpThread, (LPVOID)slisten, 0, NULL);
if(handle == NULL)
{
log_print("客户端监听线程创建失败!");
return ;
}
acpt_handle = handle;
log_print("开始监听...等待连接!");
//closesocket(slisten);
//WSACleanup();
}
void print_menu()
{
printf("************************************\n");
printf("*****************欢迎***************\n");
printf("************************************\n\n");
printf("************ 1、查看所有客户端********\n");
printf("************ 0、关闭服务器并退出*****\n");
printf("************************************\n");
}
void print_client(int index)
{
printf("************************************\n");
printf("索引:%d\t", index);
printf("端口号:%d\t\n", g_client[index].port);
printf("************************************\n");
}
void print_all()
{
int optVal;
int optLen = sizeof(int);
int i;
for(i=0;i<g_num;i++)
{
print_client(i);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
unsigned int ch;
init_client();
start();
while(true)
{
print_menu();
scanf("%u", &ch);
getchar();
switch(ch)
{
case 0:
send_all("[系统消息]:服务器关闭,再见!");
printf("退出!\n");
WSACleanup();
exit(0);
break;
case 1:
print_all();
break;
default:
printf("输入错误,重新选择!\n");
break;
}
}
return 0;
}
客户端:
// client.cpp
#include <WINSOCK2.H>
#include <STDIO.H>
#pragma comment(lib,"ws2_32.lib")
int MyRevThread(LPVOID lpParam)
{
SOCKET sClient = SOCKET(lpParam);
char revData[255];
int keeplive = true;
while(keeplive)
{
//接收数据
int ret = recv(sClient, revData, 255, 0);
if(ret > 0)
{
revData[ret] = 0x00;
printf("\n收到一条消息:\n");
printf(revData);
printf("\n");
}
else if(ret == -1)
{
char * log="";
closesocket(sClient);
keeplive = false;
break;
}
}
return 0;
}
int main(int argc, char* argv[])
{
unsigned int port = 0;
printf("输入服务器端口号:");
scanf("%u", &port);
WORD sockVersion = MAKEWORD(2,2);
WSADATA data;
if(WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sclient == INVALID_SOCKET)
{
printf("invalid socket !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(port);
serAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR)
{
printf("connect error !");
closesocket(sclient);
return 0;
}
HANDLE handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyRevThread, (LPVOID)sclient, 0, NULL);
if(handle == NULL)
{
printf("消息接收线程创建失败!");
return -1;
}
printf("连接服务器成功!\n");
while(1)
{
char input[30]="";
scanf("%s", &input);
char * sendData = input;
send(sclient, sendData, strlen(sendData), 0);
}
closesocket(sclient);
WSACleanup();
return 0;
}
搞起!
原创文章,转载请注明出处:
https://jerrycoding.com/article/socket-chat
微信
支付宝