一个简单的socket客户端和服务端的例子

来源:互联网 发布:window的端口共有多少 编辑:程序博客网 时间:2024/06/01 08:33

网络编程如TCP socket编程  位于应用层跟传输层之间, 通过socket编程让人们解脱与传输层到网络层跟链路层的传输, 我们只需要设置好socket编程接口就好,

1.socket建立socket编程对象, 设置好是IPV4 还是IPV6  以及是TCP  还是UDP 传输等属性

2.bind 为我们建好的socket绑定好IP地址(用来区分计算机) 、端口号(用来区分到底是目标计算机的那个应用程序,数据传输过来时先被目标电脑的NIC卡网络接口卡接收,里面含有端口号,一个套接字对于一个端口号这样才可以找到应用程序,端口号是16位构成 0-65535,但0-1023一般系统用了。虽然不同TCP 连接端口号不能重复,但是TCP跟UDP传输可以用同一个端口号),计算机想网络通信必须要有自己的唯一IP地址,交换机连接局域网(LAN)内多个节点,用于局域网内的数据交换,也就是组建局域网用。 路由器则是连接局域网和广域网(WAN)的,用于不同网段之间的数据交换,局域网内各节点都是通过路由器和外网进行信息交换的。现在的简易路由器通常都还带有交换机(确切讲是集线器)的功能(1个WAN口 + 4个LAN口)如宿舍常用的TPLink。一般我们用系统自带的设置好IP跟Port, 

CPU 在内存保存数据时 有大端序 高位字节存放到低位地址。小端序 高位字节存放到高位地址 俩中方式。 网络传输约定用大端序。

字节序转换, htons  ntohs    n代表network网络 h待变host主机  h to  n short       htonl  ntohl

 根据数据传输方式不同,TCP 流传输,面向连接的传输。    UDP 面向信息的传输。 TCP/IP协议族设计到理论7层实际4层 由上到下很多层面的知识,为了传输每层实现规范化的接口编程。

链路层 定义实现网卡跟LAN  WAN 等网络标准。    IP层 面向消息不可靠传输自动规划路线进行传输, 

TCP/UDP层IP层只是负责数据传输但是无安全跟顺序保证  简言之就是只管努力送到,为了保证安全引入了 TCP 三次握手四次挥手 数据重传

应用层  上面的三层都是在套接字内部实现的,网络编程大部分内容也是关于应用层的编写实现。

3.listen 进入等待连接请求状态, 服务器端 调用了listen  客户端才能调用connect函数, 服务器处于等待连接请求状态4.accept  接收client端socket请求,accept()函数返回值为自动创建的socket 用来进行数据I/O 操作,
有时为了服务器端一直可以接收数据可以用while(1)循环 accept后面部分, 实现循环回声客户端,但由于是Client发给server端 要等待server端传回来数据,等待多久? 我们可以先接收数据的大小  然后没接收全的时候 一直接受ing。
TCP (流控制是关键 一对一) 会自动控制数据流大小,有滑动窗口协议,我们write时 是把数据输到缓冲区  。TCP三次握手四次挥手   SYN ACK 。而关于UDP传输没有listen 跟accept 步骤。用sendto 跟recvfrom实现数据传输。TCP时client端用connect实现IP port的连接,UDP的时在sendto 设置IP port 如果没有则自动分配, UDP传输发几次就要收几次。UDP 虽然没有listen跟accept但是每次用时都要给套接字注册IP Port 然后传输data 删除注册的IP Port 。 为了减少耗时我们可以自己给UDP设置创建已经连接UDP 套接字。
Linux 调用close Windows调用closesocket 都是直接把socket建立的双向通道断开了,为了优雅断开引入Half-close半关闭也就是Server端发送完数据了 如果client直接close client发送的感谢言语都无法再传给server, 而如果我们只调用半关闭 让server无法发数据可以接收 还可以收到感谢信。半关闭函数为shutdown 函数 可以指定socket跟关闭方式。
DNS是IP地址和域名进行相互转换的系统,核心是DNS服务器。ping www.baidu.com 出现IP 浏览器输入IP 也可以到达百度首页,域名不可变一般 而IP地址可以变化。生活中用域名方便仅此而已, 用域名获得IP gethostbyname(char* hostname)返回hostent(域名对应的结构体)    同时还有一个gethostbyaddr 根据IP获取域名。先阶段写的程序都是创建套接字后直接使用,但是有时我们可以对套接字的特性进行一些操作。

Socket详解

网络编程就是通过编程让电脑跟电脑之间实现连接通信,一般情况下操作系统会提供套接字socket 这样的部件 来实现网络编程 我们按照框架填写参数就好。

网络编程中最基础的就是socket的操作,这里记录一下socket的基础操作有哪些,分别是什么作用,最后以一个简单的客户端和服务端例子收尾。

socket是什么?

socket起源于Unix,秉承着一切皆文件的思想,socket也是   打开 -读写- 关闭 这样的模式的一个实现。socket用于不同主机间进程的通信,而每个进程由 所使用的协议,Ip,端口号,三者决定,有兴趣的可以百度一下 多路分解和多路复用。


让我们看一下socket在TCP/IP中所处的位置


可以看到,socket位于应用层与传输层之间,作为一层抽象层把tcp/IP的复杂操作抽象成了一些简单的接口供应用层来调用。


socket的基本操作

主要介绍如下几个函数

int socket(int domain , int type, int protocol)

该函数创建一个socket套接字,返回一个socket描述符,相当于对该套接字的引用,在接下来的函数中将会用到它。

我们来看看各个参数,这里只介绍一些楼主使用过的

domain : 协议族,常用的就是AF_INET,AF_INET6,AF_LOCAL等几种(还有不少),它最关键的作用是指定了地址格式,比如说AF_INET(事实上楼主至今也只用了它..),它决定了该socket套接字的地址必须使用 32位的ipv4地址和16位的port号

type :类型,当然指的就是创建的socket类型了,常见的有如下两种,SOCK_STREAM,SOCK_DGRAM(还有不少),分别对应TCP套接字和UDP套接字,至于tcp和udp协议,大家可以去看一下我之前的博文关于tcp和udp的一些事。

protocol: 协议,有IPPROTO_TCP,IPPROTO_UDP等几种(还有不少),显然就是对应于传输层TCP和UDP协议,这里我经常用0,当传入0的时候,会使用socket类型对应的默认传输协议


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

该函数用于给socket绑定一个地址,之前说过了不同的协议族有对应不同的地址,我们以AF_INET,即ipv4+port来解释,先看看sockaddr结构体有哪些成员

struct sockaddr {
        sa_family_t     sa_family;    /* address family, AF_xxx       */
        char            sa_data[14];    /* 14 bytes of protocol address */

}共16个字节

但是我们传入的参数一般是 sockaddr_in结构体,我们来看看sockaddr_in结构体的成员有哪些

struct sockaddr_in
{
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;                     /* Port number. */
    struct in_addr sin_addr;            /* Internet address. */

    /* Pad to size of `struct sockaddr'. */
    unsigned char sin_zero[sizeof (struct sockaddr) -
                           __SOCKADDR_COMMON_SIZE -
                           sizeof (in_port_t) -
                           sizeof (struct in_addr)];     
                           /* 字符数组sin_zero[8]的存在是为了保证结构体struct sockaddr_in的大小和结构体struct sockaddr的大小相等 */
};

其实也就是方便我们使用,可以直接指定ip和port(当然这里有主机字节序和网络字节序的问题),而不需要去操作字符串了


int listen(int sockfd,int backlog)

该函数在服务端编程会用到,用于监听某个端口,至于是哪个就看你传入进去的sockfd设置的是哪一个了,backlog参数用于指定等待连接建立的socket队列的最大长度,成功返回0,失败为-1.

int accept(int sockfd, struct sockaddr* addr,socklen_t addrlen)

该函数返回与客户端建立连接的socket描述符,注意这跟监听socket不同,这是新建的一个用于与客户端通信的socket。该函数会一直阻塞到接收到一个连接为止。

第一个参数是监听socket的描述符,后面2个参数用于接受客户端的地址和地址长度。


上面2个函数都是在服务端编程中用于监听端口,被动创建与客户端的连接时,需要用到的,下面这个函数一般在客户端中需要用到的用于主动创建连接的

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

第一个参数是客户端的socket描述符,第二个参数是服务器的地址,第三个当然还是地址长度,成功的时候返回0,失败则返回-1,该函数在服务器accept后,数据到达时会返回,具体连接过程,可以百度三次握手,也可以看看我写过的tcp的一些事。


OK,连接已经建立好了,现在要进行通讯了,

常用的函数有如下4个

int read(int sockfd,void* buf ,ssize_t count)

int write(int sockfd,void* buf,ssize_t count)

int recv(int sockfd,void* buf,ssize_t count,int flags)

int send(int sockfd,void* buf,ssize_t count,int flags)

这4个函数如何使用,请参照这篇文章http://blog.csdn.NET/u011408355/article/details/45921541

server端

#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>#include <string.h>#include <arpa/inet.h>#define PORT 6888int main(){    int serv_sock;    int client_sock;    struct sockaddr_in  server;  //设置sock地址信息     if(-1==(serv_sock=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))  // 创建SOCKET   判定用何种协议族, 用TCP/UDP类型, 针对第二个参数设置不同协议   电话购买    {        printf("Create Socket Error\n");    }    memset(&server,0,sizeof(server));    server.sin_family=AF_INET;    server.sin_addr.s_addr=inet_addr("127.0.0.1");    server.sin_port=htons(PORT);    if(-1==bind(serv_sock, (struct sockaddr*)&server, sizeof(server)))  //链接IP PORT  该函数用于给socket绑定一个地址   电话号码分配    {        printf("Bind Error\n");    }    printf("Start Listen\n");        if( -1==listen(serv_sock, 5)) //监听   该函数在服务端编程会用到,用于监听某个端口,至于是哪个就看你传入进去的第一个参数设置的是哪一个了,//第二个参数用于指定等待连接建立的socket队列的最大长度    电话线路连接    {        printf("Listen Error\n");        exit(0);    }    while(1)    {        if( -1==(client_sock=accept(serv_sock,NULL,NULL) ))  //接收  该函数返回与客户端建立连接的socket描述符,注意这跟监听socket不同,  //这是新建的一个用于与客户端通信的socket。该函数会一直阻塞到接收到一个连接为止。        {            printf("Accept Error\n");            continue;        }        char buf[2048+1];        memset(buf,0, sizeof(buf));        int n = recv(client_sock ,buf, 2048, 0); //接受客户端信息 把信息放入到buf中, 返回收到的字符长度        printf("Recv msg form cilent, %d byte: ",n);        printf("%s\n",buf);        memset(buf ,0, sizeof(buf)); //初始化数组        strcpy(buf,"The Server has get the message");        send(client_sock, buf, strlen(buf), 0); //将信息送给客户端           }    close(client_sock);    close(serv_sock);    return 0;}

client 端

#include <unistd.h>#include <stdio.h>#include <stdlib.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>#include <string.h>#include <arpa/inet.h>#define PORT 6888int main(){    int conndfd;    struct sockaddr_in serverAddr;    memset(&serverAddr,0,sizeof(serverAddr));    serverAddr.sin_family=AF_INET;    serverAddr.sin_addr.s_addr=inet_addr("127.0.0.1");    serverAddr.sin_port=htons(PORT);    while(1)    {        if(-1==(conndfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))) //客户端 创建        {            printf("Create Socket Error\n");        }        if(-1==connect(conndfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr))) //客户端建立连接        {            printf("Connetc Error\n");            exit(0);        }                char buf[2048+1];        memset(buf, 0, sizeof(buf));        char Message[256];        printf("please input message:");        scanf("%s",Message);        printf("Connect Success Lets communicate!\n");        strcpy(buf, Message);        int n=send(conndfd, buf, strlen(buf),0); //将数据发送给服务器端        printf("Send %d byte to Server\n",n);        memset(buf,0,sizeof(buf));        n=recv(conndfd, buf, 2048, 0); //接受服务器端 发过来的信息        printf("Recv %d byte from Server:",n);        printf("%s\n",buf);          close(conndfd);    }      return 0;}


阅读全文
1 0
原创粉丝点击