网络编程课例简析
来源:互联网 发布:网络电影演员火不了 编辑:程序博客网 时间:2024/06/05 02:28
服务器与客户端简例
这个小例子是教师在课上讲过后让我们课后理解的题目,感觉含括了很多基础的用法,于是便单独拿出来自己看懂理解。(老师的这个驼峰命名法看得我有点难受)
宏
public.h
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>typedef unsigned int uint;#define NAME_LEN 32#define PWD_LEN 32#define PER_MAX_IO_BYTES 4096
客户端信息储存 (文件保存)
file.h
#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include "public.h"#include "list.h"#define USRINFO_PATH "./usr.info"int openFile(const char *pathname, int flags);void saveUsrInfoToFile(const char *pathname, const List *list);void getUsrInfoFromFile(const char *pathname, List *list);
file.c
#include "file.h"//打开保存文件 usr.infoint openFile(const char *pathname, int flags){ int fd = -1; fd = open(pathname, flags|O_CREAT, 0664); if (-1 == fd) { perror("open"); exit(EXIT_FAILURE); return fd;}//保存数据到 usr.infovoid saveUsrInfoToFile(const char *pathname , const List *list){ if (NULL == list) { return; } int fd = openFile(USRINFO_PATH, O_RDWR); Node *node = list->pFirstNode; while (NULL != node) { write(fd, node, sizeof(Node)); node = node->pNext; } close(fd);}//从 usr.info 中提取数据void getUsrInfoFromFile(const char *pathname , List *list){ if (NULL == list) { return; } int fd = openFile(USRINFO_PATH, O_RDWR); Node *node = NULL; while (1) { node = makeNode(); ret = read(fd, node, sizeof(Node)); if (-1 == ret || 0 == ret) { free(node); break; } node->sockfd = -1; node->pNext = NULL; insertList(list, node); } close(fd);}
客户端信息储存 (链表)
list.h
//链表结点typedef struct Node{ uint uiId; char caPwd[PWD_LEN]; int sockfd; struct Node *pNext;}Node;//链表表头typedef struct List{ int iLen; Node *pFirstNode;}List;Node *makeNode();List *makeList();void insertList(List *list, Node *node);void showList(const List *list);Node *findNodeById(uint id, const List *list);
list.c
#include "list.h"//创建客户端结点Node *makeNode(){ Node *node = (Node *)malloc(sizeof(Node)); if (NULL == node) { printf("malloc node failed\n"); exit(EXIT_FAILURE); } memset(node, 0, sizeof(Node)); return node;}//创建客户端链表(表头)List *makeList(){ List *list = (List *)malloc(sizeof(List)); if (NULL == list) { printf("malloc list failed\n"); exit(EXIT_FAILURE); } memset(list, 0, sizeof(List)); return list;}//插入客户端结点void insertList(List *list, Node *node){ if (NULL != list && NULL != node) { node->pNext = list->pFirstNode; list->pFirstNode = node; list->iLen++; }}//打印链表void showList(const List *list){ if (NULL == list) { return; } Node *node = list->pFirstNode; while (NULL != node) { printf("id:%u, pwd:%s, sockfd:%d\t", node->uiId, node->caPwd, node->sockfd); node = node->pNext; } putchar('\n');}//根据 ID 查找客户端结点Node *findNodeById(uint id, const List *list){ Node *node = NULL; if (NULL != list) { node = list->pFirstNode; while (NULL != node) { if (id == node->uiId) { break; } node = node->pNext; } } return node;}
创建协议
protocol.h
#include "public.h"enum ENUM_MSG_TYPE{ ENUM_MSG_TYPE_MIN = 0, ENUM_MSG_TYPE_REGIST_REQUEST, //注册请求 ENUM_MSG_TYPE_REGIST_RESPOND, //注册回复 ENUM_MSG_TYPE_LOGIN_REQUEST, //登录请求 ENUM_MSG_TYPE_LOGIN_RESPOND, //登录回复 ENUM_MSG_TYPE_PRIVATE_CHAT_REQUEST, //私聊请求 ENUM_MSG_TYPE_PRIVATE_CHAT_RESPOND, //私聊回复 ENUM_MSG_TYPE_GROUP_CHAT_REQUEST, //群聊请求 ENUM_MSG_TYPE_GROUP_CHAT_RESPOND, //群聊回复 ENUM_MSG_TYPE_EXIT_REQUEST, //退出请求 ENUM_MSG_TYPE_EXIT_RESPOND, //退出回复 ENUM_MSG_TYPE_MAX = 0x00ffffff};#define LOGIN_OK "ok"#define LOGIN_FAILED "failed"//协议数据单元typedef struct PDU{ uint uiPDULen; //消息的总的大小 uint uiMsgType; //消息的类型 uint uiFrom; //消息发送者的 id uint uiTo; //消息接收者的 id uint uiMsgLen; //实际消息的大小 char caMsg[4]; //实际消息(弹性数组)}PDU;PDU *makePDU(uint uiMsgLen);void sendPDU(int sockfd, PDU *pdu);PDU *recvPDU(int sockfd);
protocol.c
#include "protocol.h"//根据 协议 和 信息数据大小 创建 PDU 数据包PDU *makePDU(uint uiMsgLen){ //打包后信息数据的总大小 uint uiPDULen = sizeof(PDU)-4*sizeof(char)+uiMsgLen; PDU *pdu = (PDU *)malloc(uiPDULen); if (NULL == pdu) { printf("malloc pdu failed\n"); exit(EXIT_FAILURE); } memset(pdu, 0, uiPDULen); pdu->uiPDULen = uiPDULen; pdu->uiMsgLen = uiMsgLen; return pdu;}//把数据信息写入到创建好的 PDU 数据包中并发送void sendPDU(int sockfd, PDU *pdu){ if (NULL == pdu) { return; } int iSended = 0; int iLeft = pdu->uiPDULen; int ret = -1; //写入数据并发送 while (iLeft) { if (iLeft > PER_MAX_IO_BYTES) { ret = write(sockfd, (char *)pdu+iSended, PER_MAX_IO_BYTES); } else { ret = write(sockfd, (char *)pdu+iSended, iLeft); } if (-1 == ret) { perror("send pdu write"); break; } iSended += ret; iLeft -= ret; }}//接收 PDU 数据包PDU *recvPDU(int sockfd){ uint uiPDULen = 0; int ret = -1; //接收数据 ret = read(sockfd, &uiPDULen, sizeof(uint)); if (0 == ret || -1 == ret) { return NULL; } //从 PDU 中提取信息大小 uint uiMsgLen = uiPDULen-(sizeof(PDU)-4*sizeof(char)); PDU *pdu = makePDU(uiMsgLen); //已接收的数据大小 int iRecved = sizeof(uint); //未接收的数据大小 int iLeft = uiPDULen-sizeof(uint); //把接收的 PDU 中的 msg 转存入到新建的 PDU 中 while (iLeft) { if (PER_MAX_IO_BYTES < iLeft) { ret = read(sockfd, (char*)pdu+iRecved, PER_MAX_IO_BYTES); } else { ret = read(sockfd, (char*)pdu+iRecved, iLeft); } if (-1 == ret || 0 == ret) { break; } iRecved += ret; iLeft -= ret; } return pdu;}
服务器
server.h
#include "public.h"void setBaseId();int makeSocket();void makeBind(int sockfd);void makeListen(int sockfd);void acceptClient(int sockfd);
server.c
#include "server.h"#include "list.h"#include "protocol.h"#include "file.h"#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <pthread.h>List *g_pList = NULL;uint g_uiBaseId = 1000;//处理注册请求static void handleRegistRequest(int sockfd, PDU *pdu){ //产生节点用于保留客户端的注册信息并存入链表 //eg: id 和 密码 Node *node = makeNode(); node->sockfd = -1; node->uiId = g_uiBaseId; strncpy(node->caPwd, pdu->caMsg, PWD_LEN); insertList(g_pList, node); //将链表中的数据写入文件 saveUsrInfoToFile(USRINFO_PATH, g_pList); //给客户端产生一个注册回复 PDU *respdu = makePDU(0); respdu->uiMsgType = ENUM_MSG_TYPE_REGIST_RESPOND; respdu->uiTo = g_uiBaseId; g_uiBaseId++; sendPDU(sockfd, respdu); free(respdu);}//处理登录请求static void handleLoginRequest(int sockfd, PDU *pdu){ if (NULL == pdu) { return; } Node *node = g_pList->pFirstNode; //从列表中查找并对比登录客户端信息 while (NULL != node) { if (node->uiId == pdu->uiFrom && 0 == strncmp(node->caPwd, pdu->caMsg, PWD_LEN)) { if (-1 == node->sockfd) { node->sockfd = sockfd; } else { node = NULL; } break; } node = node->pNext; } PDU *respdu = NULL; //发送登录成功(失败)信息到客户端 if (NULL != node) { respdu = makePDU(strlen(LOGIN_OK)); strncpy(respdu->caMsg, LOGIN_OK, strlen(LOGIN_OK)); } else { respdu = makePDU(strlen(LOGIN_FAILED)); strncpy(respdu->caMsg, LOGIN_FAILED, strlen(LOGIN_FAILED)); } respdu->uiMsgType = ENUM_MSG_TYPE_LOGIN_RESPOND; sendPDU(sockfd, respdu); free(respdu);}//处理私聊处理static void handlePrivateChatRequest(int sockfd, PDU *pdu){ if (NULL == pdu) { return; } Node *node = g_pList->pFirstNode; //查找目标客户端并发送 while (NULL != node) { if (node->uiId == pdu->uiTo) { sendPDU(node->sockfd, pdu); break; } node = node->pNext; }}//处理群聊请求static void handleGroupChatRequest(int sockfd, PDU *pdu){ if (NULL == pdu) { return; } Node *node = g_pList->pFirstNode; //遍历客户端列表并发送信息 while (NULL != node) { if (node->sockfd > 0) { sendPDU(node->sockfd, pdu); } node = node->pNext; }}//处理退出请求static void handleExitRequest(int sockfd, PDU *pdu){ if (NULL == pdu) { return; } Node *node = findNodeById(pdu->uiFrom, g_pList); if (NULL != node) { node->sockfd = -1; }}//处理客户端的请求static void *handleClient(void *arg){ int sockfd = (int)arg; PDU *pdu = NULL; while (1) { //接收客户端的数据 pdu = recvPDU(sockfd); if (NULL == pdu) { pthread_exit(NULL); } //判断消息类型 //根据消息类型的不同做出不同的处理 switch (pdu->uiMsgType) { //处理客户端的注册请求 case ENUM_MSG_TYPE_REGIST_REQUEST: handleRegistRequest(sockfd, pdu); break; //处理客户端的登录请求 case ENUM_MSG_TYPE_LOGIN_REQUEST: handleLoginRequest(sockfd, pdu); break; //处理客户端的私聊请求 case ENUM_MSG_TYPE_PRIVATE_CHAT_REQUEST: handlePrivateChatRequest(sockfd, pdu); break; //处理客户端的群聊请求 case ENUM_MSG_TYPE_GROUP_CHAT_REQUEST: handleGroupChatRequest(sockfd, pdu); break; //处理客户端的退出请求 case ENUM_MSG_TYPE_EXIT_REQUEST: handleExitRequest(sockfd, pdu); printf("线程退出\n"); pthread_exit(NULL); break; default: break; } free(pdu); }}//设置新注册用户的起始idvoid setBaseId(){ Node *node = g_pList->pFirstNode; int sign = 0; //若有新客户端注册则更新 g_uiBaseId,方便给新客户端分配 ID while (NULL != node) { if (g_uiBaseId < node->uiId) { g_uiBaseId = node->uiId; sign = 1; } node = node->pNext; } if (1 == sign) { g_uiBaseId++; }}//创建 sockfd 通信int makeSocket(){ //产生socket用于监听客户端的连接 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == sockfd) { perror("socket"); exit(EXIT_FAILURE); } return sockfd;}//绑定端口void makeBind(int sockfd){ struct sockaddr_in servAddr; servAddr.sin_family = AF_INET; servAddr.sin_port = htons(8888); servAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); bzero(servAddr.sin_zero, 8); //将sockfd和特定的ip及端口绑定 //表示通过该scokfd来监听从绑定的ip连接过来的 //作用于指定端口的客户端 int ret = -1; ret = bind(sockfd, (struct sockaddr *)&servAddr, sizeof(servAddr)); if (-1 == ret) { perror("bind"); exit(EXIT_FAILURE); }}//监听 sockfd 通信void makeListen(int sockfd){ //设置该sockfd每次能够处理的最大客户端数 int ret = listen(sockfd, 10); if (-1 == ret) { perror("listen"); exit(EXIT_FAILURE); }}//接受客户端连接请求void acceptClient(int sockfd){ struct sockaddr_in clientAddr; int clientSockfd = -1; int iLen = sizeof(clientAddr); pthread_t thread; while (1) { printf("等待客户端的连接...\n"); //阻塞等待客户端的连接 //若有客户端连接过来, //则会自动将客户端的相应信息存入clientAddr中 //然后往下执行 //若有客户端连接服务器成功 //则产生一个新的socket //该新的socket用于服务器和客户端数据交换 clientSockfd = accept(sockfd, (struct sockaddr *)&clientAddr, &iLen); if (-1 == clientSockfd) { perror("accept"); break; } //inet_ntoa:将整形表示的ip // 转换成点分十进制表示的ip printf("ip为%s的客户端连接到服务器\n", inet_ntoa(clientAddr.sin_addr)); printf("产生的新的用于数据交换的sockfd:%d\n", clientSockfd); //每来一个客户端的连接 //创建一个新的线程来专门处理该客户端 pthread_create(&thread, NULL, handleClient, (void *)clientSockfd); }}
smain.c
#include "server.h"#include "list.h"#include "file.h"extern List *g_pList;int main(void){ //产生一个链表,用于保存客户端的信息 g_pList = makeList(); //从文件中获得之前注册的用户数据 getUsrInfoFromFile(USRINFO_PATH, g_pList); showList(g_pList); //设置新注册用户的起始id setBaseId(); int sockfd = makeSocket(); makeBind(sockfd); makeListen(sockfd); acceptClient(sockfd); return 0;}
客户端
client.h
#include "public.h"int makeSocket();void connectToServer(int sockfd);int loginOrRegistFace();void loginOrRegist(int sockfd);void chat(int sockfd);void exitPrograms(int sockfd);
client.c
#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h> //htons#include "client.h"#include "protocol.h"uint g_uiId = 0;//处理接收的聊天信息static void handleChat(PDU *pdu){ if (NULL == pdu) { return; } printf("%u says: \n", pdu->uiFrom); write(STDOUT_FILENO, pdu->caMsg, pdu->uiMsgLen);}//循环接收服务器的信息并处理void *handleServer(void *arg){ int sockfd = (int)arg; PDU *pdu = NULL; while (1) { //接收服务器的消息 pdu = recvPDU(sockfd); if (NULL == pdu) { printf("和服务器已断开\n"); exit(0); } //根据消息的类型做出不同的处理 switch (pdu->uiMsgType) { //处理服务器返回的注册回复信息 case ENUM_MSG_TYPE_PRIVATE_CHAT_REQUEST: case ENUM_MSG_TYPE_GROUP_CHAT_REQUEST: handleChat(pdu); break; default: break; } free(pdu); }}//创建 sockfd 通信int makeSocket(){ //AF_INET:ipv4 //SOCK_STREAM:使用可靠传输-->tcp //SOCK_DGRAM:非可靠传输-->udp //0: 使用传输默认的协议 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == sockfd) { perror("socket"); exit(EXIT_FAILURE); } return sockfd;}//连接客户端到服务器void connectToServer(int sockfd){ struct sockaddr_in servAddr; servAddr.sin_family = AF_INET; //htons:表示将主机字节序转换为网络字节序 //字节序:大端字节序,小端字节序 //端口用来标识应用 servAddr.sin_port = htons(8888); //设置要连接的服务器的ip地址 //inet_addr:将点分十进制表示的ip转换成整数表示 servAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); //将指定地址的开始往后的多少个字节置为0 bzero(servAddr.sin_zero, 8); int ret = -1; //连接服务器 ret = connect(sockfd, (struct sockaddr *)&servAddr, sizeof(servAddr)); if (-1 == ret) { perror("connect"); exit(EXIT_FAILURE); } printf("connect to server success\n");}//登录菜单int loginOrRegistFace(){ printf(" 欢迎\n"); printf("1,登录\n"); printf("2,注册\n"); printf("0,退出\n"); printf("请输入选项:\n"); int num = 0; scanf("%d", &num); return num;} //客户端注册static void regist(int sockfd){ PDU *pdu = makePDU(PWD_LEN); pdu->uiMsgType = ENUM_MSG_TYPE_REGIST_REQUEST; printf("请输入注册需要的密码:\n"); scanf("%s", pdu->caMsg); sendPDU(sockfd, pdu); free(pdu); pdu = recvPDU(sockfd); if (ENUM_MSG_TYPE_REGIST_RESPOND == pdu->uiMsgType) { g_uiId = pdu->uiTo; printf("获得注册的id: %u\n", g_uiId); } else { printf("注册失败\n"); } free(pdu);}//客户端登录static int login(int sockfd){ int id = 0; PDU *pdu = makePDU(PWD_LEN); printf("请输入id:\n"); scanf("%u", &pdu->uiFrom); id = pdu->uiFrom; printf("请输入密码:\n"); scanf("%s", pdu->caMsg); pdu->uiMsgType = ENUM_MSG_TYPE_LOGIN_REQUEST; sendPDU(sockfd, pdu); free(pdu); pdu = recvPDU(sockfd); if (ENUM_MSG_TYPE_LOGIN_RESPOND && 0 == strncmp(LOGIN_OK, pdu->caMsg, pdu->uiMsgLen)) { printf("登录成功\n"); g_uiId = id; return 1; } printf("登录失败\n"); write(STDOUT_FILENO, pdu->caMsg, pdu->uiMsgLen); putchar('\n'); return -1;}//退出客户端void exitPrograms(int sockfd){ PDU *pdu = makePDU(0); pdu->uiFrom = g_uiId; pdu->uiMsgType = ENUM_MSG_TYPE_EXIT_REQUEST; sendPDU(sockfd, pdu); free(pdu); printf("发送退出请求\n"); }//登录菜单选项处理void loginOrRegist(int sockfd){ int num = -1; int ret = -1; while (1) { num = loginOrRegistFace(); switch (num) { case 1: ret = login(sockfd); break; case 2: regist(sockfd); break; case 0: exitPrograms(sockfd); exit(EXIT_SUCCESS); default: printf("输入有误!!!\n"); break; } if (1 == ret) { break; } }}//聊天菜单static int chatFace(){ printf(" ^_^\n"); printf("1,私聊\n"); printf("2,群聊\n"); printf("0,返回\n"); printf("请输入选项:\n"); int num = 0; scanf("%d", &num); return num;}//私聊信息的输入和发送static void privateChat(int sockfd){ printf("请输入聊天的对象:\n"); uint perid = 0; scanf("%u", &perid); char caMsg[PER_MAX_IO_BYTES] = {'\0'}; printf("请输入聊天信息:\n"); read(STDIN_FILENO, caMsg , PER_MAX_IO_BYTES); PDU *pdu = makePDU(strlen(caMsg)); pdu->uiFrom = g_uiId; pdu->uiTo = perid; strncpy(pdu->caMsg, caMsg, strlen(caMsg)); pdu->uiMsgType = ENUM_MSG_TYPE_PRIVATE_CHAT_REQUEST; sendPDU(sockfd, pdu); free(pdu);}//群聊信息的输入和发送static void groupChat(int sockfd){ char caMsg[PER_MAX_IO_BYTES] = {'\0'}; printf("请输入聊天信息:\n"); read(STDIN_FILENO, caMsg, PER_MAX_IO_BYTES); PDU *pdu = makePDU(strlen(caMsg)); pdu->uiFrom = g_uiId; strncpy(pdu->caMsg, caMsg, strlen(caMsg)); pdu->uiMsgType = ENUM_MSG_TYPE_GROUP_CHAT_REQUEST; sendPDU(sockfd, pdu); free(pdu);}//聊天菜单选项处理void chat(int sockfd){ pthread_t thread; pthread_create(&thread, NULL, handleServer, (void *)sockfd); int num = -1; while (1) { num = chatFace(); switch (num) { case 1: privateChat(sockfd); break; case 2: groupChat(sockfd); break; case 0: pthread_cancel(thread); return; default: break; } }}
cmain.c
#include "client.h"int main(void){ int sockfd = makeSocket(); connectToServer(sockfd); char sign = '\0'; while (1) { //login or regist loginOrRegist(sockfd); chat(sockfd); printf("退出程序?Y/y\n"); getchar(); sign = getchar(); getchar(); if ('y' == sign || 'Y' == sign) { exitPrograms(sockfd); break; } } return 0;}