C语言实现简单的hello/hi网络聊天程序

一、TCP/IP协议通信原理

使用TCP套接字编程可以实现基于TCP/IP协议的面向连接的通信,它分为服务器端和客户端两部分,其主要实现过程如下图所示:

C语言实现简单的hello/hi网络聊天程序

(1)连接建立:服务器调用socket()、 bind()、 listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。

(2)数据传输:建立连接后, TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。

(3)关闭连接:如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。注意,任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。

二、函数描述

(1)socket函数:为了执行网络输入输出,一个进程必须做的第一件事就是调用socket函数获得一个文件描述符。

-----------------------------------------------------------------

 #include <sys/socket.h>

 int socket(int family,int type,int protocol);    

 返回:非负描述字---成功   -1---失败

 -----------------------------------------------------------------

 (2)connect函数:当用socket建立了套接口后,可以调用connect为这个套接字指明远程端的地址;如果是字节流套接口,connect就使用三次握手建立一个连接;如果是数据报套接口,connect仅指明远程端地址,而不向它发送任何数据。

-----------------------------------------------------------------

 #include <sys/socket.h>      

  int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);  

 返回:0---成功   -1---失败

 -----------------------------------------------------------------

(3)bind函数:为套接口分配一个本地IP和协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合;如指定端口为0,调用bind时内核将选择一个临时端口,如果指定一个通配IP地址,则要等到建立连接后内核才选择一个本地IP地址。

-------------------------------------------------------------------

#include <sys/socket.h>  

 int bind(int sockfd, const struct sockaddr * server, socklen_t addrlen);

 返回:0---成功   -1---失败 

 -------------------------------------------------------------------

(4)listen函数:listen函数仅被TCP服务器调用,它的作用是将用sock创建的主动套接口转换成被动套接口,并等待来自客户端的连接请求。

-------------------------------------------------------------------

#include <sys/socket.h>

 int listen(int sockfd,int backlog);   

 返回:0---成功   -1---失败

 -------------------------------------------------------------------

(5)accept函数:accept函数由TCP服务器调用,从已完成连接队列头返回一个已完成连接,如果完成连接队列为空,则进程进入睡眠状态。

-------------------------------------------------------------------

#include <sys/socket.h>         

 int accept(int listenfd, struct sockaddr *client, socklen_t * addrlen);  

  回:非负描述字---成功   -1---失败

 -------------------------------------------------------------------

 (6)write和read函数:当服务器和客户端的连接建立起来后,就可以进行数据传输了,服务器和客户端用各自的套接字描述符进行读/写操作。因为套接字描述符也是一种文件描述符,所以可以用文件读/写函数write()和read()进行接收和发送操作。

(I)write()函数用于数据的发送。                                                    

-------------------------------------------------------------------

#include <unistd.h>         

 int write(int sockfd, char *buf, int len); 

  返回:非负---成功   -1---失败

 ------------------------------------------------------------------- 

 (II)read()函数用于数据的接收。

-------------------------------------------------------------------

#include <unistd.h>         

 int read(int sockfd, char *buf, intlen);  

  回:非负---成功   -1---失败

 -------------------------------------------------------------------

三、编程步骤及代码实现

《一》服务器端

编程步骤:

(1)使用WSAStartup()函数检查系统协议栈安装情况

(2)使用socket()函数创建服务器端通信套接字

(3)使用bind()函数将创建的套接字与服务器地址绑定

(4)使用listen()函数使服务器套接字做好接收连接请求准备

(5)使用accept()接收来自客户端由connect()函数发出的连接请求

(6)根据连接请求建立连接后,使用send()函数发送数据,或者使用recv()函数接收数据

(7)使用closesocket()函数关闭套接字(可以先用shutdown()函数先关闭读写通道)

(8)最后调用WSACleanup()函数结束Winsock Sockets API

代码实现:

#include <stdio.h>
#include  <windows.h>
#pragma comment(lib,"ws2_32.lib")//WSAStartup、WSACleanup和SOCKET的动态库
int main()
{
    //1 请求协议版本
    WSADATA wsadata;
    WSAStartup(MAKEWORD(2,2), &wsadata);//请求协议版本2.2
    if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
        printf("请求协议失败\n");
        return -1;
    }
    //2 创建socket
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (SOCKET_ERROR == serverSocket) {
        printf("创建socket失败\n");
        WSACleanup();
        return -2;
    }
    printf("创建socket成功\n");
    //3创建协议地址族
    SOCKADDR_IN addr = { 0 };
    addr.sin_family = AF_INET;//协议版本
    addr.sin_addr.S_un.S_addr = inet_addr("192.168.43.3");//用自己IP
    addr.sin_port = htons(10086);//0-65535  一般取10000左右
    //4 绑定
    int r = bind(serverSocket, (sockaddr*)&addr, sizeof addr);
    if (r == -1) {
        printf("绑定失败\n");
        closesocket(serverSocket);
        WSACleanup();
        return -2;
    }
    printf("绑定成功\n");
    //5 监听
    r = listen(serverSocket, 10);//监听10个用户
    if (r == -1) {
        printf("监听失败\n");
        closesocket(serverSocket);
        WSACleanup();
        return -2;
    }
    printf("监听成功\n");
    //6 等待客户端连接 阻塞 尾声抱柱
    SOCKADDR_IN cAddr = { 0 };
    int len = sizeof cAddr;
    SOCKET clientSocket = accept(serverSocket, (sockaddr*)&cAddr, &len);
    if (SOCKET_ERROR == clientSocket) {
        printf("服务器宕机了\n");
        //关闭socket
        closesocket(serverSocket);
        //清除协议信息
        WSACleanup();
        return -2;
    }
    printf("有客户端连接到服务器:%s\n", inet_ntoa(cAddr.sin_addr));//将整数型IP地址转化为字符串
    // 7 通信
    char buff[1024];
    while (1) {
        r = recv(clientSocket, buff, 1023, NULL);
        if (r > 0) {
            buff[r] = 0;//添加‘\0‘
            printf(">>%s\n", buff);
        }
        send(clientSocket, buf, sizeof(buf), 0);
    }
    return 0;
}

《二》客户端

编程步骤:

(1)使用WSAStartup()函数检查系统协议栈安装情况

(2)使用socket()函数创建客户端套接字

(3)使用connect()函数发出也服务器建立连接的请求(调用前可以不用bind()端口号,由系统自动完成)

(4)连接建立后使用send()函数发送数据,或使用recv()函数接收数据

使用closesocet()函数关闭套接字

(5)最后调用WSACleanup()函数,结束Winsock Sockets API

代码实现:

#include <iostream>
#include <WinSock2.h>
#include <string>
#pragma warning(disable:4996)
#include <stdio.h>
#include  <windows.h>
#pragma comment(lib,"ws2_32.lib")//WSAStartup、WSACleanup和SOCKET的动态库
int main()
{
    //1 请求协议版本
    WSADATA wsadata;
    WSAStartup(MAKEWORD(2, 2), &wsadata);//请求协议版本2.2
    if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
        printf("请求协议失败\n");
        return -1;
    }
    //2 创建socket
    SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (SOCKET_ERROR == clientSocket) {
        printf("创建socket失败\n");
        WSACleanup();
        return -2;
    }
    printf("创建socket成功\n");
    //3获取协议地址族
    SOCKADDR_IN addr = { 0 };
    addr.sin_family = AF_INET;//协议版本
    addr.sin_addr.S_un.S_addr = inet_addr("192.168.43.3");//用自己IP
    addr.sin_port = htons(10086);//0-65535  一般取10000左右
     //4 连接服务器
    int r = connect(clientSocket, (sockaddr*)&addr, sizeof addr);
    if (r == -1) {
        printf("连接服务器失败\n");
        return -1;
    }
    printf("连接服务器成功\n");
    //5 通信

    char buf[1024];
    string str;
    int i;
    cin >> str;
    for (i = 0; i < str.length(); i++)
    {
        buf[i] = str[i];
    }
    buf[i] = ‘\0‘;
    send(clientSocket, buf, sizeof(buf), 0);
    // 接收数据
    recv(clientSocket, buf, sizeof(buf), 0);
    return 0;
}

四、运行结果

1、数据传送之前先启动服务器端,再启动客户端:

启动服务器后,服务器界面如下,此时客户端还未启动或请求连接。

 C语言实现简单的hello/hi网络聊天程序

 2、启动客户端后,与服务器的连接建立成功,两者界面如下图:

C语言实现简单的hello/hi网络聊天程序

 C语言实现简单的hello/hi网络聊天程序

 3、连接建立成功后,开始传输字符串聊天,结果如下:

 C语言实现简单的hello/hi网络聊天程序

 网络聊天实验完成

相关推荐