码迷,mamicode.com
首页 > 其他好文 > 详细

UNIX网络编程:socket套接字(TCP与UDP)

时间:2016-06-12 03:10:16      阅读:254      评论:0      收藏:0      [点我收藏+]

标签:

套接字简介:

套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行,Linux所提供的功能(如打印服务,ftp等)通常都是通过套接字来进行通信的,套接字的创建和使用与管道是有区别的,因为套接字明确地将客户和服务器区分出来,套接字可以实现将多个客户连接到一个服务器。

套接字创建流程以及所用到的函数:

首先socket通信中需要以下几个结构体:

struct in_addr
{
        in_addr_t  s_addr;          //32位IPV4地址
}

struct sockaddr_in
{
       uint8_t         sin_len;           //结构体长度
       sa_family_t   sin_family;      //协议族
       in_port_t      sin_port;         //端口号
       struct in_addr   sin_addr;   //32位 IPV4地址
       char                   sin_zero[8]    //保留
}

1、创建套接字:
int socket(int domain, int type, int protocol); //使用前创建一个新的套接字

参数1(domain):选择创建的套接字所用的协议族;常用的有以下选择:
AF_INET : IPv4协议;
AF_INET6: IPv6协议;
AF_LOCAL: Unix域协议;
AF_ROUTE:路由套接口;
AF_KEY :密钥套接口。
参数2(type):指定套接口类型,所选类型有:
SOCK_STREAM:字节流套接字;
SOCK_DGRAM : 数据报套接字;
SOCK_RAW : 原始套接口。
procotol: 使用的特定协议,一般使用默认协议(NULL)。
2、指定本地地址
int bind(int socket, const struct sockaddr *address, socklen_t address_len); //将套接字地址与所创建的套接字号联系起来。
参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
参数2(address):指向特定协议的地址指针。
参数3(address_len):上面地址结构的长度。
返回值:没有错误,bind()返回0,否则SOCKET_ERROR。
3、建立套接字连接

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);        //客户端请求连接
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);       //服务器接受连接工作

参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
参数2(address):指向特定协议的地址指针。
参数3(addrlen):上面地址结构的长度。
返回值:没有错误,bind()返回0,否则SOCKET_ERROR。
4、监听连接
int listen(int sockfd, int backlog); //用于面向连接服务器,表明它愿意接收连接。
参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
参数2(backlog):所监听的端口队列大小。
5、数据传输
ssize_t send(int sockfd, const void *buf, size_t len, int flags); //发送数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags); 接收数据

参数1(buf):指向存有传输数据的缓冲区的指针
参数2(len):缓冲区长度。
参数3(flags):flags的值或为0,或由下面常值中选择一个:
MSGD_DONTROUTE:不查路由表 (send可选)
MSG_DONTWAIT : 本操作不阻塞 (send、recv均可选)
MSG_OOB : 发送或接收带外数据。 (send、recv均可选)
MSG_PEEK : 查看外来的消息 (recv可选)
MSG_WAITALL : 等待所有数据 (recv可选)

6、关闭套接字
int close(int fd); //关闭套接字
参数(fd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

TCP与UDP:
(TCP)传输控制协议,面向连接。是一种提供可靠数据传输的通用协议。
(UDP)用户数据报协议,是一个面向无连接的协议。采用该协议不需要两个应用程序先建立连接。UDP协议不提供差错恢复,不能提供数据重传,因此该协议传输数据安全性差。

TCP示现客户—服务器程序:
TCP客户—服务器程序的执行流程图:
技术分享

TCP客户—服务器程序代码:
Ser.cpp:

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;

#define SERVER_PORT  5050               //端口号
#define SERVER_IP    "192.168.3.254"    //服务器ip
#define QUEUE_SIZE   5                  //所监听端口队列大小

int main(int argc, char *argv[])
{
    //创建一个套接字,并检测是否创建成功
    int sockSer;                        
    sockSer = socket(AF_INET, SOCK_STREAM, 0);
    if(sockSer == -1){
        perror("socket");
    }

    //设置端口可以重用,可以多个客户端连接同一个端口,并检测是否设置成功
    int yes = 1;
    if(setsockopt(sockSer, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
        perror("setsockopt");
    }

    struct sockaddr_in addrSer,addrCli;        //创建一个记录地址信息的结构体
    addrSer.sin_family = AF_INET;              //所使用AF_INET协议族
    addrSer.sin_port = htons(SERVER_PORT);     //设置地址结构体中的端口号
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);   //设置其中的服务器ip

    //将套接字地址与所创建的套接字号联系起来。并检测是否绑定成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("bind");

    listen(sockSer, QUEUE_SIZE);       //监听端口队列是否由连接请求,如果有就将该端口设置位可连接状态,等待服务器接收连接

    printf("Server Wait Client Accept......\n");
    //如果监听到有连接请求接受连接请求。并检测是否连接成功,成功返回0,否则返回-1
    int sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen);
    if(sockConn == -1)
        perror("accept");
    else
    {
        printf("Server Accept Client OK.\n");
        printf("Client IP:> %s\n", inet_ntoa(addrCli.sin_addr));
        printf("Client Port:> %d\n",ntohs(addrCli.sin_port));
    }

    char sendbuf[256];         //申请一个发送缓存区
    char recvbuf[256];         //申请一个接收缓存区
    while(1)
    {
        printf("Ser:>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit",4) == 0)    //如果所要发送的数据为"quit",则直接退出。
            break;
        send(sockConn, sendbuf, strlen(sendbuf)+1, 0);   //发送数据
        recv(sockConn, recvbuf, 256, 0);    //接收客户端发送的数据
        printf("Cli:> %s\n",recvbuf);
    }

    close(sockSer);         //关闭套接字
    return 0;
}

cli.cpp:

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
using namespace std;

#define SERVER_PORT  5050
#define SERVER_IP    "192.168.3.254"

int main(int argc, char *argv[])
{
    //创建客户端套接字号,并检测是否创建成功
    int sockCli;
    sockCli = socket(AF_INET, SOCK_STREAM, 0);
    if(sockCli == -1)
        perror("socket");

    //创建一个地址信息结构体,并对其内容进行设置
    struct sockaddr_in addrSer;     
    addrSer.sin_family = AF_INET;         //使用AF_INET协议族
    addrSer.sin_port = htons(SERVER_PORT);  //设置端口号
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);   //设置服务器ip

    bind(sockCli,(struct sockaddr*)&addrCli, sizeof(struct sockaddr));    //将套接字地址与所创建的套接字号联系起来

    //创建一个与服务器的连接,并检测连接是否成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = connect(sockCli,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("connect");
    else
        printf("Client Connect Server OK.\n");

    char sendbuf[256];     //申请一个发送数据缓存区
    char recvbuf[256];     //申请一个接收数据缓存区
    while(1)
    {
        recv(sockCli, recvbuf, 256, 0);    //接收来自服务器的数据
        printf("Ser:> %s\n",recvbuf);
        printf("Cli:>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit", 4) == 0)    //如果客户端发送的数据为"quit",则退出。
            break;
        send(sockCli, sendbuf, strlen(sendbuf)+1, 0);   //发送数据
    }
    close(sockCli);       //关闭套接字
    return 0;
}

运行结果:
技术分享

UDP实现客户端—服务器通信:
技术分享

相对与TCP来说,UDP安全性差,面向无链接。所以UDP地实现少了连接与接收连接的操作。所以在收发数据时就不能再用send()和recvfrom()了,而是用sendto()和recvto()之名从哪收发数据。

sendto():
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
参数2(buf):指向存有发送数据的缓冲区的指针
参数3(len):缓冲区长度。
参数4(flags):flags的值或为0,或由下面常值中选择一个:
MSGD_DONTROUTE:不查路由表 (send可选)
MSG_DONTWAIT : 本操作不阻塞 (send、recv均可选)
MSG_OOB : 发送或接收带外数据。 (send、recv均可选)
MSG_PEEK : 查看外来的消息 (recv可选)
MSG_WAITALL : 等待所有数据 (recv可选)
参数5(dest_addr):指向目的地址的指针。
参数6(addrlen) : 目的地址的长度。

recvfrom():
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
参数2(buf):指向存有接收数据的缓冲区的指针
参数3(len):缓冲区长度。
参数4(flags):flags的值或为0,或由下面常值中选择一个:
MSGD_DONTROUTE:不查路由表 (send可选)
MSG_DONTWAIT : 本操作不阻塞 (send、recv均可选)
MSG_OOB : 发送或接收带外数据。 (send、recv均可选)
MSG_PEEK : 查看外来的消息 (recv可选)
MSG_WAITALL : 等待所有数据 (recv可选)
参数5(src_addr):指向源地址的指针。
参数6(addrlen) : 源地址的长度。

程序代码:
ser.cpp:

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

int main()
{
    //创建一个套接字,并检测是否创建成功
    int sockSer = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockSer == -1)
        perror("socket");

    struct sockaddr_in addrSer;  //创建一个记录地址信息的结构体 
    addrSer.sin_family = AF_INET;    //使用AF_INET协议族 
    addrSer.sin_port = htons(5050);     //设置地址结构体中的端口号
    addrSer.sin_addr.s_addr = inet_addr("192.168.3.169");  //设置通信ip

    //将套接字地址与所创建的套接字号联系起来,并检测是否绑定成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("bind");

    char sendbuf[256];    //申请一个发送数据缓存区
    char recvbuf[256];    //申请一个接收数据缓存区
    struct sockaddr_in addrCli;
    while(1)
    {
        recvfrom(sockSer,recvbuf,256,0,(struct  sockaddr*)&addrCli, &addrlen);     //从指定地址接收客户端数据
        printf("Cli:>%s\n",recvbuf);

        printf("Ser:>");    
        scanf("%s",sendbuf);
        sendto(sockSer,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr*)&addrCli, addrlen);    //向客户端发送数据
    }
    return 0;
}

cli.cpp:

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

int main()
{
    //创建一个套接字,并检测是否创建成功
    int sockCli = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockCli == -1){
        perror("socket");
    }

    addrSer.sin_family = AF_INET;    //使用AF_INET协议族 
    addrSer.sin_port = htons(5050);     //设置地址结构体中的端口号
    addrSer.sin_addr.s_addr = inet_addr("192.168.3.169");  //设置通信ip
    socklen_t addrlen = sizeof(struct sockaddr);


    char sendbuf[256];    //申请一个发送数据缓存区
    char recvbuf[256];    //申请一个接收数据缓存区

    while(1){
        //向客户端发送数据
        printf("Cli:>");
        scanf("%s",sendbuf);
        sendto(sockCli, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr*)&addrSer, addrlen);   
        接收来自客户端的数据
        recvfrom(sockCli, recvbuf, BUFFER_SIZE, 0, (struct sockaddr*)&addrSer, &addrlen);
        printf("Ser:>%s\n", recvbuf);

    }

    return 0;
}

运行结果:
技术分享

UDP的池先中没有对客户端与服务器所发送数据是否为“quit”进行判断。判断方法和TCP中的相同。可以将其完善。

UNIX网络编程:socket套接字(TCP与UDP)

标签:

原文地址:http://blog.csdn.net/dandelion_gong/article/details/51602209

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!