TCP编程

客户端-服务器配置

TCP/IP编程通常以客户端-服务器形式展现,有3中典型的客户端-服务器配置情况,如图1.


典型主机配置
图1:典型的客户端-服务器配置情况

  • 客户端和服务器在同一台机器上。
    这种配置不包含物理网络,是最简单的一种。输出数据照常沿着TCP/IP协议栈向下传送,但数据会被内部环回,像输入数据一样传回协议栈顶,不会被放到网络设备输出队列中去。即时客户端和服务器最终运行在不同的机器上,在开发过程中采用这种配置也有几个优点。首先,由于没有网络时延,采用这种配置更容易判断客户端和服务器应用程序的原始性能。第二,这种方法提供了理想的实验环境分组不会被丢弃、延迟,传输时也不会失序

  • 客户端和服务器在同一个局域网内。
    这种情况的典型情况就是打印服务器。一个小型的局域网可能为多台主机只配置一台打印机。其中一个主机(或者内建了TCP/IP协议的打印机)作为服务器使用,接收来自其它主机的打印请求。

  • 客户端和服务器被广域网隔开。
    WAN可以是因特网,也可以是公司内部网,这时两个应用程序不在同一个局域网中

基本套接字API

socket()

通常我们需要做的第一件事就是获取套接字。可以通过socket系统调用来实现。

#include <sys/socket.h> /*UNIX*/
#include <winsock2.h>  /*Windows*/

SOCKET socket(int domain,int type,int protocol);
/*返回:成功时返回套接字描述符,失败时返回-1(UNIX系统)或者INVALID_SOCKET(Windows)*/

domain:套接字API可以支持几个不公的通信域(communication domain)。domain参数是一个常量,用来表示所期望的通信域。常见的有AF_INET(AddressFamily_Internet,表示的是因特网)和AF_LOCAL(或AF_UNIX)。AF_LOCAL域用于同一台机器上的IPC(InterProcess Communication,进程间通信)。
type:说明要创建的套接字类型。最常见的有:
- SOCK_STREAM.这些套接字提供一个可靠的、全双工、面向连接的字节流。在TCP/IP中,就表示TCP。
- SOCK_DGRAM.这些套接字提供的是一种不可靠的、尽力而为的数据报服务。在TCP/IP中,就表示UDP。
- SOCK_RAW.这些套接字允许对IP层的某些数据报进行访问。可用于一些特殊目的,比如监听ICMP报文。
protocol:这个字段说明了应该在套接字上使用哪种协议。对TCP/IP来说,这个字段通常都是由套接字类型隐式说明,参数设置为0。在某些情况下,比如对原始套接字来说,有几种可能的协议,就需要制定希望使用的协议。

connect()

对简单的TCP客户端来说,与对等实体建立会话时需要使用的其它套接字API只有connect一种。

#include <sys/socket.h> /*UNIX*/
#include <winsock2.h>  /*Windows*/

int connect(SOCKET sock,const struct sockaddr *peer,int peer_len);
/*返回:成功时返回0,失败时返回-1(UNIX)或非零值(Windows)*/

sock:参数sock是socket调用返回的套接字描述符。
peer:参数peer指向一个地址结构,这个地址结构装载了期望的对等实体地址和其它一些信息。对AF_INET域来说就是一个sockaddr_in结构
peer_len:参数peer_len是peer所指结构的长度大小。

recv()和send()

一旦连接建立起来,就可以传送数据了。在UNIX中,可以像文件描述符那样,直接用套接字描述符来调用read和write。但Windows并没有用套接字语法重载这些系统调用。不过,我们可以用recv和send来代替。除了包含一个额外的参数外,这些调用和read和write是一样的。

#include <sys/socket.h> /*UNIX*/
#include <winsock2.h>  /*Windows*/

int recv(SOCKET sock,void *buf,size_t len,int flags);
int send(SOCKET sock,const void *buf,size_t len,int flags);
/*返回:成功时返回传回的字节数,失败时返回-1*/

参数sock、buf和len与read和write函数的参数一样。参数flags的取值通常与系统有关,但UNIX和Windows都支持下列值:
- MSG_OOB:将此标志置位时,会发送或读取紧急数据。
- MSG_PEEK:这个标记用来查看输入数据,但不会将其从接收缓冲区中删除。执行调用之后,接下来的都操作依然可以读到那些数据。
- MSG_DONTROUTE:这个标记会使内核绕过通常的路由函数。通常只有路由程序使用,或者用于诊断。

recvfrom()和sendto()

对于TCP来说,上面的调用基本上就能满足了。但如果使用UDP的话,recvfrom和sendto调用也很有用。这些调用允许我们在发送UDP数据报时指定目的地址,在读取UDP数据报时将源地址解析出来。

#include <sys/socket.h> /*UNIX*/
#include <winsock2.h>  /*Windows*/

int recvfrom(SOCKET sock,void *buf,size_t len,int flags,
struct sockaddr *from,int *from_len);
int sendto(SOCKET sock,const void *buf,size_t len,int flags,
struct sockaddr *to,int to_len);
/*返回:成功时返回传送的字节数,失败时返回-1*/

前面四个参数——sock、buf、len和flags与recv和send调用中相应参数的含义相同。
from:指向一个套接字地址结构,内核在这个地址中存储了输入数据报的源地址。
from_len:指向一个整数,表示地址的长度。注意,from_len是一个指向整数的指针。
sento:指向一个套接字地址结构,这个结构包含了数据报的目的地址。
to_len:*to指向的地址结构的长度。注意,to_len就是一个整数,而不是一个指针。*

客户端程序

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>//perror()
#include <stdlib.h>//exit()

int main(void)
{
    //创建对等实体地址
    struct sockaddr_in peer;
    peer.sin_family=AF_INET;
    peer.sin_port=htons(7500);
    peer.sin_addr.s_addr=inet_addr("127.0.0.1");

    //获取套接字
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if (sock<0)
    {
        perror("socket call failed!\n");
        exit(1);
    }

    //连接到对等实体
    int con=connect(sock,(struct sockaddr *)&peer,sizeof(peer));
    if (con<0)
    {
        perror("connect call failed!\n");
        exit(1);
    }

    //发送、接收字节
    int send_count=send(sock,"1",1,0);
    if (send_count<=0)
    {
        perror("send call failed!\n");
        exit(1);
    }
    char buf[1];
    int recv_count=recv(sock,buf,1,0);
    if (recv_count<=0)
    {
        perror("recv call failed!\n");
        exit(1);
    }
    else
    {
        printf("%c\n",buf[0]);
    }
    exit(0);
}

要测试客户端程序,我们还需要一个服务器。服务器的套接字调用和客户端的略有不同。

bind()和listen()

服务器必须在其知名端口上监听客户连接。这是通过listen调用来实现这项功能的,但首先服务器必须将接口地址和知名端口号绑定到它的监听套接字上去,这是用bind调用来实现的。

#include <sys/socket.h> /*UNIX*/
#include <winsock2.h>  /*Windows*/

int bind(SOCKET sock,const struct sockaddr *local,int local_len);
/*返回:成功时返回0,失败时返回-1(UNIX)或者SOCKET_ERROR(Windows)*/

sock:参数sock是监听套接字的描述符。
local:参数local提供了要监听的端口和接口。通常被设置为INADDR_ANY,说明任何接口都会接收这个连接。如果多宿主主机希望仅在一个接口上接受连接,它可以指定那个接口的IP地址。
local_len:结构sockaddr_in的长度。

将本地地址绑定到套接字之后,就可以开始对连接进行套接字监听。这是用系统调用listen来实现看,它唯一的任务就是把套接字标识为监听状态。当主机收到一个连接请求时,内核会搜索监听套接字列表,查找与连接请求中目的地址和端口号相匹配的那个套接字。

#include <sys/socket.h> /*UNIX*/
#include <winsock2.h>  /*Windows*/

int listen(SOCKET sock,int backlog);
/*返回:成功时返回0,失败时返回-1(UNIX)或者SOCKET_ERROR(Windows)*/

sock:参数sock是标识为监听状态的套接字描述符。
backlog:指定挂起连接的最大数量。他并不是在指定端口上同时可建立连接的最大值,而是排队等待应用程序接受的连接的最大数量。这个值设置成多大是和系统有关的,必须查看系统文档来为特定的机器确定恰当的值。

accept()

最后一个套接字调用是accept系统调用,负责接受已完成连接队列中的连接。

#include <sys/socket.h> /*UNIX*/
#include <winsock2.h>  /*Windows*/

SOCKET accept(SOCKET sock,struct sockaddr *addr,int *addr_len);
/*返回:成功返回一个连接好的套接字,失败返回-1(UNIX)或者INVALID_SOCKET(Windows)*/

sock:参数sock是监听套接字的描述符。
addr;指向sockaddr_in结构体。
addr_len:指向结构体长度的整数。
通常我们并不关心对等体的地址,这种情况下会将addr和addr_len都设置为NULL。

服务器端程序

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>//perror()
#include <stdlib.h>//exit()

int main(void)
{
    int rc;
    //填充地址
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(7500);
    local.sin_addr.s_addr=htonl(INADDR_ANY);

    //获取套接字
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if (sock<0)
    {
        perror("socket call failed!\n");
        exit(1);
    }

    //绑定知名端口
    rc=bind(sock,(struct sockaddr *)&local,sizeof(local));
    if (rc<0)
    {
        perror("bind call failed!\n");
        exit(1);
    }

    //监听套接字
    rc=listen(sock,5);    
    if (rc<0)
    {
        perror("listen call failed!\n");
        exit(1);
    }

    //接受连接
    int sock1=accept(sock,NULL,NULL);
    if (sock1<0)
    {
        perror("accept call failed!\n");
        exit(1);
    }

    //传输数据
    char buf[1];
    rc=recv(sock1,buf,1,0);
    if (rc<0)
    {
        perror("recv call failed!\n");
        exit(1);
    }
    printf("%c\n",buf[0]);
    rc=send(sock1,"2",1,0);
    if (rc<0)
    {
        perror("send call failed!\n");
        exit(1);
    }
    exit(0);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值