基本的TCP套接字编程

上图基本展现了TCP客户端与服务器编程的基本的流程。ios

一、面向链接编程(TCP编程

面向链接的网络应用程序开发流程比较固定,须要开发者建立服务器与客户端两个应用程序,经过网络是想进程间的通信。服务器

●     服务器端流程网络

1        建立套接字(socket数据结构

2        服务绑定(bind并发

3        服务侦听(listensocket

4        处理新到链接(accept函数

5       数据收发(recv\sendui

6       套接字关闭(closespa

●     客户端流程

客户端套接字建立(socket

发起链接(connect)

③  数据发收(send\recv)

 ④ 套接字关闭(close)

下面大体介绍下上述函数的一些基本的用法:

(1)socket函数:
    函数原型:
   int socket(int family,int type, int protocol)

      socket()函数用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源。若是协议protocol未指定(等于0),则使用缺省的链接方式。 对于使用一给定地址族的某一特定套接口,只支持一种协议。但地址族可设为AF_UNSPEC(未指定),这样的话协议参数就要指定了。协议号特定于进行通信的“通信域”。

     支持下述类型描述: 

     SOCK_STREAM 提供有序的、可靠的、双向的和基于链接的字节流,使用带外数据传送机制,为Internet地址族使用TCP。    SOCK_DGRAM 支持无链接的、不可靠的和使用固定大小(一般很小)缓冲区的数据报服务,为Internet地址族使用UDP。       SOCK_STREAM类型的套接口为全双向的字节流。对于流类套接口,在接收或发送数据前必需处于已链接状态。用connect()调用创建与另外一套接口的链接,链接成功后,便可用send()和recv()传送数据。当会话结束后,调用close()。带外数据根据规定用send()和recv()来接收。 实现SOCK_STREAM类型套接口的通信协议保证数据不会丢失也不会重复。若是终端协议有缓冲区空间,且数据不能在必定时间成功发送,则认为链接中断,其后续的调用也将以WSAETIMEOUT错误返回。 SOCK_DGRAM类型套接口容许使用sendto()和recvfrom()从任意端口发送或接收数据报。若是这样一个套接口connect()与一个指定端口链接,则可用send()和recv()与该端口进行数据报的发送与接收

(2)connect函数

   函数原型:

   int connect(intsockfd,const struct sockaddr *servaddr,socklen_t addrlen)

connect()用来将参数sockfd 的socket 连至参数serv_addr 指定的网络地址. 结构sockaddr请参考bind(). 参数addrlen 为sockaddr 的结构长度.
  返回值:成功则返回0, 失败返回-1, 错误缘由存于errno 中

(3)bind函数:

   当一个套接字用socket()建立后,存在一个名字空间(地址族),但它没有被命名。bind()将套接字地址(包括本地主机地址和本地端口地址)与所建立的套接字号联系起来,即将名字赋予套接字,以指定本地半相关。其调用格式以下:

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

    参数s是由socket()调用返回的而且未做链接的套接字描述符(套接字号)。参数name赋给套接字s的本地地址(名字),其长度可变,结构随通讯域的不一样而不一样。

namelen代表了name的长度。

    若是没有错误发生,bind()返回0。不然返回值SOCKET_ERROR

    地址在创建套接字通讯过程当中起着重要做用,做为一个网络应用程序设计者对套接字地址结构必须有明确认识。例如,UNIX有一组描述套接字地址的数据结构,其中使用TCP/IP

协议的地址结构为:

struct sockaddr_in{

      short sin_family;                 /*AF_INET*/

      u_short sin_port;                 /*16位端口号,网络字节顺序*/

      struct in_addr sin_addr;    /*32IP地址,网络字节顺序*/

      char sin_zero[8];                 /*保留*/

}

(4)accept函数:

accept()的调用格式以下:

SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);

    参数s为本地套接字描述符,在用作accept()调用的参数前应该先调用过listen()addr向客户方套接字地址结构的指针,用来接收链接实体的地址。addr的确切格式由套接字建立时创建的地址族决定。addrlen为客户方套接字地址的长度(字节数)。

若是没有错误发生,accept()返回一个SOCKET类型的值,表示接收到的套接字的描述符。不然返回值INVALID_SOCKET

    accept()用于面向链接服务器。参数addraddrlen存放客户方的地址信息。调用前,参addr指向一个初始值为空的地址结构,而addrlen的初始值为0;调用accept()后,服务器等待从编号为s的套接字上接受客户链接请求,而链接请求是由客户方的connect()调用发出的。当有链接请求到达时,accept()调用将请求链接队列上的第一个客户方套接字地址及长度放入addraddrlen,并建立一个与s有相同特性的新套接字号。新的套接字可用于处理服务器并发请求。

    四个套接字系统调用,socket()bind()connect()accept(),能够完成一个彻底五元相关的创建。socket()指定五元组中的协议元,它的用法与是否为客户或服务器、是否面向链接无关。bind()指定五元组中的本地二元,即本地主机地址和端口号,其用法与是否面向连接有关:在服务器方,不管是否面向链接,均要调用bind();在客户方,若采用面向链接,则能够不调用bind(),而经过connect()自动完成。若采用无链接,客户方必须使用bind()得到一个惟一的地址。

    以上讨论仅对客户/服务器模式而言,实际上套接字的使用是很是灵活的,惟一需遵循的原则是进程通讯以前,必须创建完整的相关。

(5)listen函数:

监听链接──listen()

    此调用用于面向链接服务器,代表它愿意接收链接。listen()需在accept()以前调用,其调用格式以下:

int PASCAL FAR listen(SOCKET s, int backlog);

   参数s标识一个本地已创建、还没有链接的套接字号,服务器愿意从它上面接收请求。

backlog表示请求链接队列的最大长度,用于限制排队请求的个数,目前容许的最大值为5。若是没有错误发生,listen()返回0。不然它返回SOCKET_ERROR

    listen()在执行调用过程当中可为没有调用过bind()的套接字s完成所必须的链接,并创建长度为backlog的请求链接队列。

    调用listen()是服务器接收一个链接请求的四个步骤中的第三步。它在调用socket()分配一个流套接字,且调用bind()s赋于一个名字以后调用,并且必定要在accept()以前调用。

下面给出一个简单的TCP客户端服务器的例子:

服务器(server.cpp):

#include<iostream>
#include<unistd.h>
#include<stdio.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<string.h>
#define SERVER_PORT 5049
#define SERVER_IP "127.0.0.1"

int main(int argc,char*argv[])
{
    int listenfd,sockconn;

    listenfd = socket(AF_INET,SOCK_STREAM,0);
    if(listenfd < 0)
        perror("socket");

    struct sockaddr_in servaddr,cliaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
    servaddr.sin_port = htons(SERVER_PORT);

    int res = bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    if(res == -1)
        perror("bind");

    if(listen(listenfd,3) < 0)
        perror("listen");

    socklen_t  clilen = sizeof(cliaddr);
    sockconn = accept(listenfd,(struct sockaddr *)&cliaddr,&clilen);
    if(sockconn == -1)
        perror("accept");

    char sendbuf[256];
    char recvbuf[256];
    while(1)
    {
        printf("Ser:>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit",4) == 0)
               break;
        send(sockconn,sendbuf,strlen(sendbuf) + 1,0);

        recv(sockconn,recvbuf,256,0);
        printf("Cli:>%s\n",recvbuf);
    }
    close(listenfd);
    return 0;
}

客户端(client .cpp

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

#define SERV_PORT 5049
#define ADDR "127.0.0.1"
int main(int argc,char*argv[])
{
    int sockfd;
    struct sockaddr_in servaddr;

    sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
        perror("socket");

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = inet_addr(ADDR);

    int sockconn = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    if(sockconn == -1)
        perror("connect");

    char sendbuf[256];
    char recvbuf[256];
    
    while(1)
    {
        recv(sockfd,recvbuf,256,0);
        printf("Ser:>%s\n",recvbuf);

        printf("Cli:>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit",4) == 0)
            break;

        send(sockfd,sendbuf,strlen(sendbuf) + 1,0);
    }
    close(sockfd);
    return 0;
}

其大体的执行结果:

从执行结果能够看出:该程序基本实现到了客户端与服务器的通讯,但其中还存在了不少的问题好比:
(1)该程序只能实现一对一的通讯,也就是说一个服务器只能对一个客户端服务。

 (2)该程序只能是服务器发一句,客户端接收一句,不能实现服务器或客户端连着发的状况

针对上述的问题咱们给出一个升级的版本,能够实现上述的问题:

服务器:

#include"unp.h"

pthread_t tid[2];

void *write_fun(void*arg)
{
    int sockSer = *(int *)arg;
    char sendbuf[BUFF_SIZE];
    memset(sendbuf,0,BUFF_SIZE);
    while(1)
    {
        printf(":>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit",4) == 0)
        {
            break;
        }
        send(sockSer,sendbuf,strlen(sendbuf) + 1,0);
    }
    pthread_exit(NULL);
}

void * read_fun(void *arg)
{
    int sockSer = *(int *)arg;
    char recvbuf[BUFF_SIZE];
    memset(recvbuf,0,BUFF_SIZE);
    struct sockaddr_in addrCli;
    socklen_t addrlen = sizeof(struct sockaddr);
    while(1)
    {
        getsockname(sockSer,(struct sockaddr *)&addrCli,&addrlen);
        recv(sockSer,recvbuf,BUFF_SIZE,0);
        if(strncmp(recvbuf,"quit",4) == 0)
        {
            break;
        }

        printf("Client[%d]:>%s\n",addrCli.sin_port,recvbuf);
    }

    
}
int main(int argc,char*argv[])
{
    int sockSer;

    sockSer = socket(AF_INET,SOCK_STREAM,0);
    if(sockSer == -1)
        perror("socket");

    struct sockaddr_in addrSer,addrCli;

    addrSer.sin_family = AF_INET;
    addrSer.sin_port = htons(SOCK_PORT);
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);

    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(sockSer,(struct sockaddr*)&addrSer,addrlen);
    if(res == -1)
        perror("bind");

    listen(sockSer,QUEUE_SIZE);
    int sockConn;
    while(1)
    {
        sockConn = accept(sockSer,(struct sockaddr*)&addrCli,&addrlen);
        if(sockConn == -1)
            perror("accept");
        else
            printf("client accept server ok.\n");

        pthread_create(&tid[0],NULL,write_fun,&sockConn);
        pthread_create(&tid[1],NULL,read_fun,&sockConn);
        
    }
    close(sockSer);
    return 0;
}

客户端:

#include"unp.h"

pthread_t tid[2];

void *write_fun(void*arg)
{
    int sockCli = *(int *)arg;
    char sendbuf[BUFF_SIZE];
    memset(sendbuf,0,BUFF_SIZE);
    while(1)
    {
        printf(":>");
        scanf("%s",sendbuf);
        send(sockCli,sendbuf,strlen(sendbuf) + 1,0);
        if(strncmp(sendbuf,"quit",4) == 0)
        {
            pthread_cancel(tid[1]);
            break;
        }
    }
    pthread_exit(NULL);
}

void * read_fun(void *arg)
{
    int sockCli = *(int *)arg;
    char recvbuf[BUFF_SIZE];
    memset(recvbuf,0,BUFF_SIZE);
    struct sockaddr_in addrCli;
    socklen_t addrlen = sizeof(struct sockaddr);
    getsockname(sockCli,(struct sockaddr *)&addrCli,&addrlen);
    while(1)
    {
        recv(sockCli,recvbuf,BUFF_SIZE,0);
        printf("%s\n",recvbuf);
    }

    
}
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;
    addrSer.sin_port = htons(SOCK_PORT);
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);

    int sockConn;
    socklen_t addrlen = sizeof(struct sockaddr);

    sockConn = connect(sockCli,(struct sockaddr*)&addrSer,addrlen);
    if(sockConn == -1)
        perror("accept");
    else
        printf("client connect server ok.\n");

    pthread_create(&tid[0],NULL,write_fun,&sockCli);
    pthread_create(&tid[1],NULL,read_fun,&sockCli);

    pthread_join(tid[0],NULL);
    pthread_join(tid[1],NULL);

    close(sockCli);
    return 0;
}

其执行结果:

从上述的执行结果能够看出,咱们已经实现了服务器或客户端连着发的问题,可是在多个客户端通讯时,遇到的问题就是服务器端没法判断是与哪个客户端进行通讯,对于这个问题咱们在之后的博客中为你们解决