计算机网络Socket编程之TCP协议

>TCP协议位于传输层,是一种面向连接的可靠的传输协议

>socket(套接字):是IP地址与端口号的统称

>套接字的基本结构

          struct sockaddr   这个结构用来存储套接字地址

结构体的定义

  struct sockaddr {

  unsigned short sa_family; /* address族, AF_xxx */

  har sa_data[14]; /* 14 bytes的协议地址 */

  };

       sa_family    一般来说,都是“AFINET”。

       sa_data      包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一切的。

为了处理struct sockaddr, 程序员建立了另外一个相似的结构 struct sockaddr_in:

   struct sockaddr_in (“in” 代表 “Internet”)

   struct sockaddr_in {

   short int sin_family; /* Internet地址族 */

   unsigned short int sin_port; /* 端口号 */

   struct in_addr sin_addr; /* Internet地址 */

   unsigned char sin_zero[8]; /* 添0(和struct sockaddr一样大小)*/

   };

这个结构提供了方便的手段来访问socket address(struct sockaddr)结构中的每一个元素

 >IP 地址转换

Linux 系统提供和很多用于转换IP 地址的函数.首先,假设你有一个struct sockaddr_in ina,并且你的IP 166.111.69.52 ,你想把你的IP 存储到ina 中。你可以使用的函数: inet_addr() ,它能够把一个用数字和点表示IP 地址的字符串转换成一个无符号长整型。你可以像下面这样使用它:

      ina.sin_addr.s_addr = inet_addr(“166.111.69.52”);

注意:

inet_addr() 返回的地址已经是网络字节顺序了,你没有必要再去调用htonl() 函数反过来,如果你有一个struct in_addr 并且你想把它代表的IP 地址打印出来(按照数字.数字.数字.数字的格式),那么你可以使用函数inet_ntoa()(“ntoa”代表“Network to ASCII”),它会把struct in_addr 里面存储的网络地址以数字.数字.数字.数字的格式。

l inet_ntoa() 使用struct in_addr 作为一个参数,不是一个长整型值。


>套接字字节转换程序的列表:

l htons()——“Host to Network Short”主机字节顺序转换为网络字节顺序(对无符号短型进行操作4 bytes)

l htonl()——“Host to Network Long” 主机字节顺序转换为网络字节顺序(对无符号长型进行操作8 bytes)

l ntohs()——“Network to Host Short “ 网络字节顺序转换为主机字节顺序(对无符号短型进行操作4 bytes)

l ntohl()——“Network to Host Long “ 网络字节顺序转换为主机字节顺序(对无符号长型进行操作8 bytes)

>基本套接字调用

socket() 函数

取得套接字描述符

socket 函数的定义是下面这样子的:

#include <sys/types.h>

#include <sys/socket.h>

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


bind() 函数

bind()函数可以帮助你指定一个套接字使用的端口。

当你使用socket() 函数得到一个套接字描述符,你也许需要将socket 绑定上一个你的机器上的端口。当你需要进行口监听 listen()操作,等待接受一个连入请求的时候,一般都需要经过这一步。比如网络泥巴(MUD),Telnet a.b.c.d 4000.如果你只是想进行连接一台服务器,也就是进行 connect() 操作的时候,这一步并不是必须的。

bind()的系统调用声明如下:

#include <sys/types.h>

#include <sys/socket.h>

int bind (int sockfd , struct sockaddr *my_addr , int addrlen) ;

参数说明:

l sockfd 是由socket()函数返回的套接字描述符。

l my_addr 是一个指向struct sockaddr 的指针,包含有关你的地址的信息:名称、端口和IP 地址。

addrlen 可以设置为sizeof(struct sockaddr)。

connect()函数

让我们花一点时间来假设你是一个Telnet 应用程序。你的使用者命令你建立一个套接字描述符。你遵从命令,调用了socket()。然后,使用者告诉你连接到“166.111.69.52”的23 端口(标准的Telnet 端口)⋯⋯你应该怎么做呢?

你很幸运:Telnet 应用程序,你现在正在阅读的就是套接字的进行网络连接部分:

connect()。

connect() 函数的定义是这样的:

#include <sys/types.h>

#include <sys/socket.h>

int connect (int sockfd, struct sockaddr *serv_addr, int addrlen);

connect()的三个参数意义如下:

l sockfd :套接字文件描述符,由socket()函数返回的。

l serv_addr 是一个存储远程计算机的IP 地址和端口信息的结构。

l addrlen 应该是sizeof(struct sockaddr)。

listen() 函数

listen()函数是等待别人连接,进行系统侦听请求的函数。当有人连接你的时候,你有两步需要做:通过listen()函数等待连接请求,然后使用accept()函数来处理。(accept()函数在下面介绍)。

listen()函数调用是非常简单的。函数声明如下:

#include <sys/socket.h>

int listen(int sockfd, int backlog);

listen()函数的参数意义如下:

l sockfd 是一个套接字描述符,由socket()系统调用获得。

l backlog 是未经过处理的连接请求队列可以容纳的最大数目。

backlog 具体一些是什么意思呢?每一个连入请求都要进入一个连入请求队列,等待listen 的程序调accept((accept()函数下面有介绍)函数来接受这个连接。当系统还没有调用accept()函数的时候,如果有很多连接,那么本地能够等待的最大数目就是backlog 的数值。你可以将其设成5 到10 之间的数值

accept()函数

函数accept()有一些难懂。当调用它的时候,大致过程是下面这样的:

有人从很远很远的地方尝试调用 connect()来连接你的机器上的某个端口(当然是你已经在listen()的)。他的连接将被 listen 加入等待队列等待accept()函数的调用(加入等待队列的最多数目由调用listen()函数的第二个参数backlog 来决定)。你调用 accept()函数,告诉他你准备连接。accept()函数将回返回一个新的套接字描述符,这个描述符就代表了这个连接.好,这时候你有了两个套接字描述符,返回给你的那个就是和远程计算机的连接,而第一个套接字描述符仍然在你的机器上原来的那个端口上listen()。这时候你所得到的那个新的套接字描述符就可以进行send()操作和recv()操作了。

下面是accept()函数的声明:

#include <sys/socket.h>

int accept(int sockfd, void *addr, int *addrlen);

accept()函数的参数意义如下:

l sockfd 是正在listen() 的一个套接字描述符。

l addr 一般是一个指向struct sockaddr_in 结构的指针;里面存储着远程连接过来的计算机的信息(比如远程计算机的IP 地址和端口)


>   下面我们就来模拟一个socket编程的TCP协议

   server.c 的作用是接受client的请求,并与client进行简单的数据通信,整体为一个阻塞式的网络聊天工具。

#####server.c
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<string.h>
#include<errno.h>
#include<arpa/inet.h>
#include<pthread.h>

static void usage(const char* proc)
{
   printf("Usage:%s [ip] [port]\n",proc);
}

void *thread_run(void* arg)
{
   printf("creat a new thread\n");
   int fd=(int)arg;
   char buf[1024];
   while(1){
           memset(buf,'\0',sizeof(buf));
           ssize_t _s=read(fd,buf,sizeof(buf)-1);
          if(_s>0){
                printf("#client:%s\n",buf);
                write(fd,buf,strlen(buf));
           }
           else if(_s==0){
       printf("client close...\n");
           break;
           }
           else{
                   printf("read error...\n");
                   break;
           }
    return (void*)0;
   }

}
int main(int argc,char* argv[])
{
        if(argc!=3){
                usage(argv[0]);
                exit(1);
        }
        int listen_sock=socket(AF_INET,SOCK_STREAM,0);
        if(listen_sock<0){
                perror("socket");
                exit(2);
        }
        struct sockaddr_in server_socket;
        server_socket.sin_family=AF_INET;
        server_socket.sin_addr.s_addr=inet_addr(argv[1]);
        server_socket.sin_port=htons(atoi(argv[2]));
        int opt=1;
        setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

        //bangding
        if(bind(listen_sock,(struct sockaddr*)&server_socket,sizeof(server_socket))<0){
                perror("bind");
                exit(3);
        }
        //listening
        listen(listen_sock,5);

        //accept
        struct sockaddr_in peer;
        socklen_t len=sizeof(peer);
        int fd=accept(listen_sock,(struct sockaddr*)&peer,&len);
        printf("fd: %d\n",fd);
        if(fd<0){
                perror("accept");
                exit(4);
        }
        printf("get a new link,socket->[%s] [%d]",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port));
       
       while(1)
       {
           pthread_t id;
           pthread_create(&id,NULL,thread_run,(void*)fd);
           //pthread_detach(id);
           pthread_join(id,NULL);
        }
       return 0;
}


######client.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<pthread.h>
#include<errno.h>
#include<stdlib.h>

static void usage(const char *proc)
{
     printf("usage:%s [ip] [port]\n",proc);
}

int main(int argc,char* argv[])
{
        if(argc!=3){
                usage(argv[0]);
                exit(1);
        }
 int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock==0){
                perror("socket");
                exit(2);
        }
    struct sockaddr_in client_sock;
        client_sock.sin_family=AF_INET;
        client_sock.sin_addr.s_addr=inet_addr(argv[1]);
        client_sock.sin_port=htons(atoi(argv[2]));

        if(connect(sock,(struct sockaddr*)&client_sock,sizeof(client_sock))<0)
        {
                perror("connect");
                exit(3);
        }
    char buf[1024];
        while(1)
 {
           memset(buf,'\0',sizeof(buf));
           printf("Please Enter:");
           fflush(stdout);
           ssize_t _s=read(0,buf,sizeof(buf)-1);
           if(_s>0)
           {
              buf[_s-1]='\0';
              write(sock,buf,strlen(buf));
              _s=read(sock,buf,sizeof(buf));
              if(_s>0)
              {
                 printf("%s\n",buf);
               }
           }
        }
        return 0;
}

运行结果:

        

 

       

   

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值