正在加载……
专注、离线、切勿分心
1.1. 使用TCP协议的流程图 

TCP通信的基本步骤如下:

服务端:socket---bind---listen---while(1){---accept---recv---send---close---}---close

客户端:socket--------------------------------connect---send---recv-------------close

   //connect , 完成3次握手      

服务器端:                 客户端

 

#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
socket函数成一个套接口描述符。
原型int socket(int domain,int type,int protocol);
参数domain    { AF_INET:Ipv4网络协议AF_INET6:IPv6网络协议}
          type        {tcp:SOCK_STREAM   udp:SOCK_DGRAM}
          protocol   指定socket所使用的传输协议编号。通常为0.
返回值:   成功则返回套接口描述符,失败返回-1。
常用实例: int sfd = socket(AF_INET, SOCK_STREAM, 0);
             
bind函数用来绑定一个端口号和IP地址,使套接口与指定的端口号和IP地址相关联。
原型int bind(  int sockfd,   struct sockaddr *my_addr,    int addrlen);
参数sockfd       为前面socket的返回值。
           my_addr   为结构体指针变量
         addrlen     sockaddr的结构体长度。通常是计算 sizeof(struct sockaddr);
struct sockaddr  //此结构体不常用
{
   unsigned short int sa_family;  //调用socket() 时的domain参数,即AF_INET值。
   char sa_data[14];              //最多使用14个字符长度
};
此sockaddr结构会因使用不同的socket domain而有不同结构定义,例如使用AF_INET domain,其socketaddr结构定义便为

struct sockaddr_in   //常用的结构体
{
      unsigned short int sin_family;     //即为sa_family  AF_INET
      uint16_t sin_port;                 //为使用的port编号
      struct in_addr sin_addr;           //为IP 地址
      unsigned char sin_zero[8];         //未使用
};
struct in_addr
{
       uint32_t s_addr;   // 可以直接等于INADDR_ANY,表示的是本机IP
};   
typedef uint32_t in_addr_t
返回值:成功则返回0,失败返回-1
常用实例struct sockaddr_in my_addr;                      //定义结构体变量
                memset(&my_addr, 0, sizeof(struct sockaddr));       //将结构体清空
//或bzero(&my_addr, sizeof(struct sockaddr));
                my_addr.sin_family = AF_INET;                       //表示采用Ipv4网络协议
                my_addr.sin_port = htons(8888);  //表示端口号为8888,通常是大于1024的一个值。
//htons()用来将参数指定的16位hostshort转换成网络字符顺序
        my_addr.sin_addr.s_addr = inet_addr("192.168.0.101"); 
//inet_addr()用来将IP地址字符串转换成网络所使用的二进制数字,如果为INADDR_ANY,这表示服务器自动填充本机IP地址。
        bind(sfd, (struct sockaddr*)&my_str, sizeof(struct socketaddr));

(注:通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。同样,通过将my_addr.sin_addr.s_addr置为 INADDR_ANY,系统会自动填入本机IP地址)
accept函数接受远程计算机的连接请求,建立起与客户机之间的通信连接。服务器处于监听状态时,如果某时刻获得客户机的连接请求,此时并不是立即处理这个请求,而是将这个请求放在等待队列中,当系统空闲时再处理客户机的连接请求。当accept函数接受一个连接时,会返回一个新的socket标识符,以后的数据传输和读取就要通过这个新的socket编号来处理,原来参数中的socket也可以继续使用,继续监听其它客户机的连接请求。(也就是说,类似于移动营业厅,如果有客户打电话给10086,此时服务器就会请求连接,处理一些事务之后,就通知一个话务员接听客户的电话,也就是说,后面的所有操作,此时已经于服务器没有关系,而是话务员跟客户的交流。对应过来,客户请求连接我们的服务器,我们服务器先做了一些绑定和监听等等操作之后,如果允许连接,则调用accept函数产生一个新的套接字,然后用这个新的套接字跟我们的客户进行收发数据。也就是说,服务器跟一个客户端连接成功,会有两个套接字。)
原型int accept(int s,struct sockaddr *addr,int *addrlen);
参数s               为前面socket的返回值. 即 sfd
             addr        为结构体指针变量,和bind的结构体是同种类型的,系统会把远程主机的信息(远程主机的地址和端口号信息)保存到这个指针所指的结构体中。
            addrlen   表示结构体的长度,为整型指针
返回值成功则返回新的socket处理代码new_fd失败返回-1
常用实例:  struct sockaddr_in clientaddr;
         memset(&clientaddr, 0, sizeof(struct sockaddr));
         int addrlen = sizeof(struct sockaddr);
         int new_fd = accept(sfd, (struct sockaddr*)&clientaddr, &addrlen);
         printf("%s %d success connect\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));


accept函数是一个阻塞函数,如果struct sockaddr 里面没有读到数据,就会一直阻塞等待。

如果不想获取远程客户端的信息,后面两个参数可以填NULL
listen函数使服务器的这个端口和IP处于监听状态,等待网络中某一客户机的连接请求。如果客户端有连接请求,端口就会接受这个连接。
原型int listen(int sockfd,int backlog);
参数sockfd     为前面 socket 的返回值.即 sfd
          backlog   指定同时能处理的最大连接要求,通常为10或者5。最大值可设至128
返回值:成功则返回0,失败返回-1
常用实例listen(sfd, 10);

sfd可读,表示有新的客户端请求链接;不可读表示没有请求链接;可读之后accept才接受新的客户端链接,完成三次握手
sfd就相当于一个容器,里面存放了指定个数的描述符(eg:10)不知道这么理解对不对,应该是错的
recv函数用新的套接字来接收远端主机传来的数据,并把数据存到由参数buf 指向的内存空间
原型:     int recv(int sockfd,void *buf,int len,unsigned int flags);
参数      sockfd     为前面accept的返回值.即new_fd,也就是新的套接字。
               buf         表示缓冲区
               len          表示缓冲区的长度
               flags       通常为0
返回值:成功则返回实际接收到的字符数,可能会少于你所指定的接收长度。失败返回-1
常用实例      char buf[512] = {0};
           recv(new_fd, buf, sizeof(buf), 0)
           puts(buf);
send函数用新的套接字发送数据给指定的远端主机
原型int send(int s,const void *msg,int len,unsigned int flags);
参数: s            为前面accept的返回值.即new_fd
          msg       一般为常量字符串
          len         表示长度
          flags      通常为0
返回值:成功则返回实际传送出去的字符数,可能会少于你所指定的发送长度。失败返回-1
常用实例send(new_fd, "hello", 6, 0) 
close函数当使用完文件后若已不再需要则可使用close()关闭该文件,并且close()会让数据写回磁盘,并释放该文件所占用的资源
原型int close(int fd);
参数fd   为前面的sfd,new_fd
返回值:若文件顺利关闭则返回0,发生错误时返回-1
常用实例:     close(new_fd);
           close(sfd);
close一个TCP套接口的默认行为是把该套接口标记为已关闭,然后立即返回到调用进程。该套接字不能在由调用进程使用,也就是说不能再作为read或write的第一个参数。close操作只是使相应套接字的引用计数减1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求
客户端独有:
connect函数来请求连接远程服务器,将参数sockfd 的socket 连至参数serv_addr 指定的服务器IP和端口号上去
原型int connect (int sockfd,struct sockaddr *serv_addr,int addrlen);
参数sockfd         为前面socket的返回值,即sfd
          serv_addr    为结构体指针变量,存储着远程服务器的IP与端口号信息。
          addrlen        表示结构体变量的长度
返回值:成功则返回0,失败返回-1
常用实例:  struct sockaddr_in seraddr;                         //请求连接服务器
                  memset(&seraddr, 0, sizeof(struct sockaddr));
                  seraddr.sin_family = AF_INET;
                  seraddr.sin_port = htons(8888);                   //服务器的端口号
                  seraddr.sin_addr.s_addr = inet_addr("192.168.0.101");        //服务器的ip
                  connect(sfd, (struct sockaddr*)&seraddr, sizeof(struct sockaddr);
func.h
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

     

tcp_server.c tcp_client.c
#include"func.h"
//使用socket接口实现tcp通信的服务器端
int main(int argc,char** argv)
{
        if(argc!=3)
        {
                printf("error args\n");
                return -1;
        }
        int sfd=socket(AF_INET,SOCK_STREAM,0);     
        if(-1==sfd)
        {
                perror("socket");
                return -1;
        }
        printf("sfd = %d\n",sfd);
        struct sockaddr_in ser;
        memset(&ser,0,sizeof(ser));
        ser.sin_family=AF_INET;
        ser.sin_port=htons(atoi(argv[2]));      //一定是htons
        ser.sin_addr.s_addr=inet_addr(argv[1]);      //只能是这个
        int ret;
        //给sfd绑定本地ip地址和端口号
        ret=bind(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
        if(-1==ret)
        {
                perror("bind");
                return -1;
        }
        //监听
        ret=listen(sfd,10);
        if(-1==ret)
        {
                perror("listen");
                return -1;
        }
        printf("I am listening\n");
        struct sockaddr_in client;
        memset(&client,0,sizeof(client));
        int addrlen=sizeof(struct sockaddr_in);
        //socklen_t addrlen=sizeof(struct sockaddr_in);
        int new_fd=accept(sfd,(struct sockaddr*)&client,&addrlen);   //阻塞函数,运行到这里会卡住,直到有客户端连接上。
        if(-1==new_fd)
        {
                perror("accept");
                return -1;
        }
        printf("new_fd = %d\n",new_fd);
        printf("client IP = %s , port = %d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
        char buf[128]={0};
        ret=recv(new_fd,buf,sizeof(buf),0);
        if(-1==ret)
        {
                perror("recv");
                return -1;
        }
        printf("recv str =%s\n",buf);
        ret=send(new_fd,"I am server",11,0);
        if(-1==ret)
        {
                perror("send");
                return -1;
        }
        close(newfd);
        close(sfd);
        return 0;
}
#include"func.h"

int main(int argc,char** argv)
{
        if(argc!=3)
        {
                printf("error args\n");
                return -1;
        }
        int sfd=socket(AF_INET,SOCK_STREAM,0);
        if(-1==sfd)
        {
                perror("socket");
                return -1;
        }
        struct sockaddr_in ser;
        memset(&ser,0,sizeof(ser));
        ser.sin_family=AF_INET;
        ser.sin_port=htons(atoi(argv[2]));
        ser.sin_addr.s_addr=inet_addr(argv[1]);
        int ret;
        ret=connect(sfd,(struct sockaddr*)&ser,sizeof(struct sockaddr));
        if(-1==ret)
        {
                perror("connect");
                return -1;
        }
        char buf[128]={0};
        ret=send(sfd,"I am client",11,0);
        if(-1==ret)
        {
                perror("send");
                return -1;
        }
        ret=recv(sfd,buf,sizeof(buf),0);
        if(-1==ret)
        {
                perror("recv");
                return -1;
        }
        printf("recv str=%s\n",buf);
        close(sfd);
        return 0;
}
//先运行服务端,再运行客户端
//先运行服务端

  


posted on 2018-03-13 08:47  正在加载……  阅读(462)  评论(0编辑  收藏  举报