SOCKET演示程序及回声客户端实现(Windows及Linux)

light


一、初识套接字
确认网络通信身份信息三要素——IP地址、MAC地址、端口号。
IP地址是 Internet Protocol Address 的缩写,译为“网际协议地址”。

一台计算机可以拥有一个独立的 IP 地址,一个局域网也可以拥有一个独立的 IP 地址(对外就好像只有一台计算机)。对于目前广泛使用 IPv4 地址,它的资源是非常有限的,一台计算机一个 IP 地址是不现实的,往往是一个局域网才拥有一个 IP 地址。

在因特网上进行通信时,必须要知道对方的 IP 地址。实际上数据包中已经附带了 IP 地址,把数据包发送给路由器以后,路由器会根据 IP 地址找到对方的地里位置,完成一次数据的传递。路由器有非常高效和智能的算法,很快就会找到目标计算机。

MAC 地址是 Media Access Control Address 的缩写,直译为“媒体访问控制地址”,也称为局域网地址(LAN Address),以太网地址(Ethernet Address)或物理地址(Physical Address)。

现实的情况是,一个局域网往往才能拥有一个独立的 IP;换句话说,IP 地址只能定位到一个局域网,无法定位到具体的一台计算机。这可怎么办呀?这样也没法通信啊。

其实,真正能唯一标识一台计算机的是 MAC 地址,每个网卡的 MAC 地址在全世界都是独一无二的。计算机出厂时,MAC 地址已经被写死到网卡里面了(当然通过某些“奇巧淫技”也是可以修改的)。局域网中的路由器/交换机会记录每台计算机的 MAC 地址。

端口(Port)是一个虚拟的、逻辑上的概念。可以将端口理解为一道门,数据通过这道门流入流出,每道门有不同的编号,就是端口号。
有了 IP 地址和 MAC 地址,虽然可以找到目标计算机,但仍然不能进行通信。一台计算机可以同时提供多种网络服务,例如 Web 服务(网站)、FTP 服务(文件传输服务)、SMTP 服务(邮箱服务)等,仅有 IP 地址和 MAC 地址,计算机虽然可以正确接收到数据包,但是却不知道要将数据包交给哪个网络程序来处理,所以通信失败。

为了区分不同的网络程序,计算机会为每个网络程序分配一个独一无二的端口号(Port Number),例如,Web 服务的端口号是 80,FTP 服务的端口号是 21,SMTP 服务的端口号是 25。
port


二、缓缓切入

新手上路,请多多关照~,这真的很重要。

Linux使用"文件描述符”的概念 , 而windows则采用"文件句柄”的概念;Linux不区分socket文件和普通文件,而windows区分;Linux下采用的是系统调用 ,返回int类型 ,而windows下为SOCKET类型,也就是句柄。
那,我们先从最容易的hello world 开始吧。

我对Linux和windows的演示程序做了详细的注释,因为涉及的面也很多辣,理论部分不写在上面了,把程序理解了 才是王道呀。。


如下所示:

Linux下socket:
① server.cpp

/*****************************************************
copyright (C), 2014-2015, Lighting Studio. Co.,     Ltd. 
File name:
Author:Jerey_Jobs    Version:0.1    Date: 
Description:
Funcion List: 
*****************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main()
{
    /**************************************************************************************************
     创建套结字参数1:ipv4地址
               参数2:面向连接型的socket
               参数3:面向连接型的TCP协议
     *************************************************************************************************/
    int serv_sock;
    if((serv_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == -1)         
    {
        perror("error:socket");
        return -1;
    }
   
    /***************************************************************************************************
     将套接字和IP,端口绑定,memset()是为了让结构体每个成员都赋值为0字节的大小.
        sin_family:使用ipv4地址
        sin_addr.s_addr:具体的ip地址(表明结构体内部仍有另一个结构体变量以访问本结构体外部的结构体成员)
        inet_addr();将点分十进制ipv4地址转换为in_addr_t类型
        sin_port:设置端口号
     bind():是servaddr_in强制转换成一个 sockaddr 类型的结构体 因为bind()需要一个sockaddr类型的结构体
     htons()是将一个16位数从主机字节顺序转换为网络字节顺序
     ***************************************************************************************************/
    struct sockaddr_in serv_addr;
    memset(&serv_addr,0,sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(1234); 
    
    bind(serv_sock,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    
    //进入监听状态,等待用户发送请求
    printf("服务器正在监听...\n");
    listen(serv_sock,20);
    
    /**********************************************************************************
    接受客户端请求,accept()将返回一个全新的套接字来与客户端通信
    addr保存了客户端的ip地址和端口号,而sock是服务器端的套接字,
    后面的接收与回声等操作将使用这个新生成的套接字
    *******************************************************************************/
    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[] = "i am a bad boy!";
    write(clnt_sock,str,sizeof(str));
    
    //关闭套接字
    close(clnt_sock);
    close(serv_sock);

    return 0;
}

② client.cpp

/*****************************************************
copyright (C), 2014-2015, Lighting Studio. Co.,     Ltd. 
File name:
Author:Jerey_Jobs    Version:0.1    Date: 
Description:
Funcion List: 
*****************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.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");//环回地址
    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 from server :%s\n",buffer);

    //关闭套接字
    close(sock);

    return 0;
}

③ 操作流程及演示结果
1.启动一个终端,编译并先运行server.cpp 。
2.启动另外一个终端,编译后运行client.cpp。

client接受到从server发送过来的字符串就运行结束了;同时,server完成传输任务,并结束运行。
演示服务器演示客户端
windows下socket:
① server.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

#pragma comment(lib,"ws2_32.lib")

int main(void)
{
	//DLL初始化
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2,2),&wsaData);

	//创建套接字
	SOCKET serv_sock;
	if((serv_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) ==-1)
	{
		perror("error socket");
		return (-1);
	}

	sockaddr_in serv_addr;
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	serv_addr.sin_port = htons(1234);

	//将套接字绑定
	bind(serv_sock, (SOCKADDR *)&serv_addr, sizeof(SOCKADDR));

	//监听
	printf("服务器正在监听中...\n");
	listen(serv_sock, 20);

	SOCKET clnt_sock;
	SOCKADDR clnt_addr;
	int clnt_addr_size = sizeof(clnt_addr);
	clnt_sock = accept(serv_sock, (SOCKADDR *)&clnt_addr, &clnt_addr_size);//等待客户端连接

	char buffer[20] = {0};
	printf("AND  i'll  send  message  to  client:");
	scanf("%[^\n]",buffer);

	send(clnt_sock, buffer, strlen(buffer)+sizeof(buffer), 0);

	closesocket(clnt_sock);
	closesocket(serv_sock);

	WSACleanup();

	return 0;
}

②. clint.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>

#pragma comment(lib,"ws2_32.lib")

int main(void)
{
	//DLL初始化
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2,2),&wsaData);

	//创建套接字
	SOCKET clnt_sock;
	if((clnt_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) <0)
	{
		perror("error : socket");
		return (-1);
	}

	sockaddr_in clnt_addr;
	memset(&clnt_addr, 0, sizeof(clnt_addr));
	clnt_addr.sin_family = AF_INET;
	clnt_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
	clnt_addr.sin_port = htons(1234);

	//客户端请求与服务器建立连接
	connect(clnt_sock , (SOCKADDR *)&clnt_addr, sizeof(SOCKADDR));

	char buffer[20] = {0};
	recv(clnt_sock, buffer, strlen(buffer)+sizeof(buffer), 0);

	printf("Message from server:%s\n",buffer);

	closesocket(clnt_sock);

	WSACleanup();

	return 0;
}

③ 操作流程及演示结果
1.将server.cpp源程序编译并执行:
使得服务器处于监听状态
server_exp
2.将client.cpp源程序编译并执行:
客户端与服务器(connect)达成一致意见:“你可以发消息过来了。”
clnt_exp
3.最后双方断开连接


三、回声初现

所谓“回声”,是指客户端向服务器发送一条数据,服务器再讲数据原样返还给客户端。就像声音一样,遇到障碍物会被“反弹”回来。


如下所示:
① linux实现:
server.cpp:

/*****************************************************
copyright (C), 2014-2015, Lighting Studio. Co.,     Ltd. 
File name:
Author:Jerey_Jobs    Version:0.1    Date: 
Description:
Funcion List: 
*****************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main()
{
    //创建套接字
    int serv_sock;
    if((serv_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == -1)
    {
        perror("error : socket");
        return -1;
    }
    
    //填入相关信息
    struct sockaddr_in serv_addr;
    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serv_addr.sin_port = htons(1234);
    
    //套接字绑定
    bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    
    printf("服务器正在监听中...\n");
    listen(serv_sock, 20);
    
    //接受服务器请求
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_len = sizeof(clnt_addr);
    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_len);
    
    //发条数据试试水
    char *str = "i am a good boy!";
    write(clnt_sock, str, strlen(str));//每次至多读到字符串长度个字节数(32位char型)
    
    //新的文件描述符将用于接收客户端发来的消息
    //读取客户端发来的消息的字节数
    char buffer[40] = {0};
    int clnt_sock_agalen = read(clnt_sock, buffer, sizeof(buffer)-1);
    printf("Message from the client:%s\n",buffer);
    int ret = write(clnt_sock,buffer,clnt_sock_agalen);//再将消息原封不动的送回去

    if(ret != -1)
        printf("消息回声成功!\n");
    
    close(clnt_sock);
    close(serv_sock);
    return 0;
}

client.cpp:

/*****************************************************
copyright (C), 2014-2015, Lighting Studio. Co.,     Ltd. 
File name:
Author:Jerey_Jobs    Version:0.1    Date: 
Description:
Funcion List: 
*****************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>


int main()
{
    //创建套接字
    int clnt_sock;
    if((clnt_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == -1)
    {
        perror("error : socket");
        return (-1);
    }

    //填入相关信息
    struct sockaddr_in clnt_addr;
    memset(&clnt_addr, 0, sizeof(clnt_addr));
    clnt_addr.sin_family = AF_INET;
    clnt_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    clnt_addr.sin_port = htons(1234);

    //建立链接
    connect(clnt_sock, (struct sockaddr *)&clnt_addr , sizeof(clnt_addr));

    //接受试水数据
    char str1[20] = {0};
    read(clnt_sock, str1, sizeof(str1));
    printf("Message from server:%s\n",str1);


    //客户端向服务器发送消息
    char str2[20] = {0};
    printf("您正在向服务器发送:");
    scanf("%[^\n]",str2);

    write(clnt_sock, str2, sizeof(str2));

    //使用一个全新的buffer用以区分服务器的回声
    //而不使用str2
    char str3[20] = {0};
    read(clnt_sock, str3, sizeof(str3));
    printf("Message from sever again:%s\n",str3);

    close(clnt_sock);

    return 0;
}

操作流程及演示结果 :
serv_rebackclint_reback


② windows下实现:

server.cpp:

#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib") //加载ws2_32.dll

#define BUF 100
 
int main(void)
{
	//初始化DLL
	WSADATA wsaData;//我们需要一个全新的wsaData结构体变量
	WSAStartup(MAKEWORD(2,2),&wsaData);//将关于ws2_32.dll的相关信息写入wsaData结构体中


	//创建套接字
	SOCKET servSock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);


	//绑定套接字
	sockaddr_in sockAddr;
	memset(&sockAddr , 0 , sizeof(sockAddr));//每个字节都要用0填充
	sockAddr.sin_family = PF_INET;//使用ipv4地址
	sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	sockAddr.sin_port = htons(1234);
	
	//将套接字绑定
	bind(servSock , (SOCKADDR *)&sockAddr , sizeof(SOCKADDR));

	//服务器监听状态
	printf("服务器进入监听状态...\n");
	listen(servSock , 20);

	//接收客户端的请求
	SOCKADDR clntAddr;
	int nSize = sizeof(clntAddr);
	SOCKET clntSock  = accept(servSock , (SOCKADDR *)&clntAddr , &nSize);
	
	//嘿嘿嘿,先发送一些数据看看能不能接收
	char *str = "hello world";
	send(clntSock ,str, strlen(str)+sizeof(str), 0);
	
	char buffer[BUF] ={0};//设置缓冲区 用来接收客户端发过来的消息
	int strlen = recv(clntSock , buffer , BUF , 0);//接收客户端发来的数据 返回接收数据的长度
	printf("Message from client : %s\n",buffer);
	send(clntSock ,buffer,strlen,0);//将数据原样返回

	//关闭套接字
	closesocket(clntSock);
	closesocket(servSock);

	//关闭DLL
	WSACleanup();

	return 0;
}

client.cpp:

#include <stdio.h>
#include <stdlib.h>
#include <Winsock2.h>

#pragma comment(lib,"ws2_32.lib")
#define BUF_SIZE 100

int main(void)
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2,2) , &wsaData);

	//创建套接字
	SOCKET sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

	//向客户端发起请求
	sockaddr_in sockAddr;
	memset(&sockAddr,0,sizeof(sockAddr));
	sockAddr.sin_family = AF_INET;//使用ipv4地址
	sockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	sockAddr.sin_port = htons(1234);

	connect(sock, (SOCKADDR *)&sockAddr, sizeof(SOCKADDR));

	//获取用户输入的字符串并发送给服务器

	char buffer1[BUF_SIZE] ={0};
	recv(sock,buffer1,BUF_SIZE,0);
	printf("Message from server : %s\n",buffer1);
	
	char buffer2[BUF_SIZE] ={0};
	printf("input string:");
	scanf("%[^\n]",buffer2);
	send(sock,buffer2,strlen(buffer2),0);
	
	//memset(bufRecv,0,BUF_SIZE);
	
	//再次接收服务器传回的数据
	char bufRecv[BUF_SIZE] = {0};
	recv(sock,bufRecv,BUF_SIZE,0);
	
	//输出接受到的数据
	printf("Message from server again: %s\n",bufRecv);
	
	closesocket(sock);

	WSACleanup();


	return 0;
}

操作流程及演示结果 :
clnt_reback
serv_reback


四、本节结语
通过几段简单的面向流格式(连接型)的套接字演示程序,可以发现:Linux与windows中的套接字存在略微的差别。

  1. Windows 下的 socket 程序依赖 Winsock.dll 或 ws2_32.dll,必须提前加载。DLL 有两种加载方式,隐式加载与显示加载。

  2. Linux 使用“文件描述符”的概念,而 Windows 使用“文件句柄”的概念;Linux 不区分 socket 文件和普通文件,而 Windows 区分;Linux 下 socket() 函数的返回值为 int 类型,而 Windows 下为 SOCKET 类型,也就是句柄。

  3. Linux 下使用 read() / write() 函数读写,而 Windows 下使用 recv() / send() 函数发送和接收。

  4. 关闭 socket 时,Linux 使用 close() 函数,而 Windows 使用 closesocket() 函数。

通过回声程序可以发现,客户端也能想服务器端发送数据,这样服务器端就可以根据不同的请求操作,做出不同的相应,http服务器就是一个典型的例子,请求的网址不同,返回的页面也就不同了。

关于socket的导学 暂时看到这儿了,当然关于更多的socket奥秘和结和性还需今后不断地求索啦。。。


天印湖

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值