1.Socket通信

本博客部分内容参考教程来自C语言中文网

进入socket通信首先要了解两个概念,第一:服务器端,第二:客户端。(区别是两者的服务对象不同。服务器端是为客户端服务的,客户端就是为真正的“客户”来服务的,所以这两者之间不同,但又密切相连,客户端是请求方或者说是指令发出方,而服务器端是响应方。)

服务器端:顾名思义是服务的,客户端发送的请求交给服务器端处理,是以response对象存在,服务器端处理完毕后反馈给客户端。

客户端:在web中是以request对象存在的,发送请求给服务器端处理,具体的使用方法可以查找javaee的servletrequest以及其子类。

一般我们访问网站,都是客户端(浏览器、app)发出请求,然后对方服务器端(sina,sohu)响应,结果就是返回了页面路径给我们,我们再根据路径看到了网页。

//服务器端代码  server.cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main(){
    //创建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//参数分别是IPV4,面向连接套接字,TCP协议

    //将套接字和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地址//将一个字符串的IP转换成一个长整型
    serv_addr.sin_port = htons(1234);  //端口 //将整数转换为网络字节
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));//将address指向的sockaddr结构体中描述的一些属性(IP地址、端口号、地址簇)与socket套接字绑定,也叫给套接字命名。

//采用TCP通信时,客户端不需要bind()他自己的IP和端口号,而服务器必须要bind()自己本机的IP和端号;
/*因为服务器是时时在监听有没有客户端的连接,如果服务器不绑定IP和端口的话,客户端上线的时候怎么连到服务器呢,所以服务器要绑定IP和端口,而客户端就不需要了,客户端上线是主动向服务器发出请求的,因为服务器已经绑定了IP和端口,所以客户端上线的就向这个IP和端口发出请求,这时因为客户开始发数据了(发上线请求),系统就给客户端分配一个随机端口,这个端口和客户端的IP会随着上线请求一起发给服务器,服务收到上线请求后就可以从中获起发此请求的客户的IP和端口,接下来服务器就可以利用获起的IP和端口给客户端回应消息了。*/

    //进入监听状态,等待用户发起请求
    listen(serv_sock, 20);

    //接收客户端请求
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

    //向客户端发送数据
    char str[] = "http://c.biancheng.net/socket/";
    write(clnt_sock, str, sizeof(str));
   
    //关闭套接字
    close(clnt_sock);
    close(serv_sock);

    return 0;
}
//客户端代码  client.cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main(){
    //创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    //向服务器(特定的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);  //端口
    connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
   
    //读取服务器传回的数据
    char buffer[40];
    read(sock, buffer, sizeof(buffer)-1);
   
    printf("Message form server: %s\n", buffer);
   
    //关闭套接字
    close(sock);

    return 0;
}

启动一个终端(Shell),先编译 server.cpp 并运行:

$ g++ server.cpp -o server
$ ./server

正常情况下,程序运行到 accept() 函数就会被阻塞,等待客户端发起请求。

接下再启动一个终端,编译 client.cpp 并运行:

$ g++ client.cpp -o client
$ ./client
Message form server: http://c.biancheng.net/socket/

client 接收到从 server发送过来的字符串就运行结束了,同时,server 完成发送字符串的任务也运行结束了。大家可以通过两个打开的终端来观察。

client 运行后,通过 connect() 函数向 server 发起请求,处于监听状态的 server 被激活,执行 accept() 函数,接受客户端的请求,然后执行 write() 函数向 client 传回数据。client 接收到传回的数据后,connect() 就运行结束了,然后使用 read() 将数据读取出来。

server 只接受一次 client 请求,当 server 向 client 传回数据后,程序就运行结束了。如果想再次接收到服务器的数据,必须再次运行 server,不能够一直接受客户端的请求。

PS:

用socket创建网络编程接口------文件描述符。 “地方”定义了,下面就需要将socket放置在这个“地方”(TCP),将他们紧紧地捆绑在一起,用bind函数吧, 我们来看看函数原型:

int PASCAL FAR bind (SOCKET s, const struct sockaddr FAR *addr, int namelen);

     第一个参数当然是待绑定的套接字啦,第二个参数是标识绑定在哪个“地方”, 第三个参数是这个“地方”的占地大小。

     返回值表示绑定操作是否成功,0表示成功, -1表示不成功。函数的返回值千万不要忽视,上次就被人说了。

    一般是这么调用的:

     iRet = bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 注意强制转换

     我们来对比一下文件I/O操作和网络I/O操作: 打开一个文件后, 便可以对文件进行读写操作了, 但是, 网络I/O实际上有三个步骤来完成这个功能:

     1. 打开/创建socket

     2. 命名socket, 我们知道, socket名称包含"协议, ip地址,  端口号"这三个要素, 而命名就是通过调用bind函数把socket与这三个要素绑定一起来。

     3. 建立连接

accept() 函数

当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。它的原型为:

int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);  //Linux
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);  //Windows

它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。

accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。

最后需要说明的是:listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。

 

发布了28 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/m0_37957160/article/details/103525679