1. 基础知识

    (1)socket

    a. 什么是socket?

      socket这个词可以表示很多概念,这儿我们讲的socket是:“IP地址+端口号(TCP或UDP端口号)”。在TCP/IP协议中,它唯一标识网络通讯中的一个进程。


    b. socket有什么用?

      在TCP协议中,建立连接的两个进程各自有一个socket来标识,这两个socket组成 的socket pair就唯一标识一个连接。socket本身有“插座”的意思,因此用来描述网络连接的一 对一关系。


    c.什么叫socketAPI

      为TCP/IP协议设计的应用层编程接口称为socketAPI。


2. 程序实现图

服务器:调用socket()、bind()、listen() 完成初始化后,调用accept()阻塞等待,处于监听端口的状

态,

客户端:调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后 从accept()返回。

套接字编程——基于TCP协议_网络连接


3. 相关函数

(1)创建套接字——socket()

套接字编程——基于TCP协议_socket_02

a.  参数

domain:表示底层通信所使用的协议,有很多选项。这儿我们选择AF_INET格式,IPv4网络协议;

type:表示协议实现的方式,也有很多选项。这儿我们用SOCK_STREAM,它 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,为Internet地址族使用TCP。 (在UDP中,我们使用参数SOCK_DGRAM ,它支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务);

protocol:套接口所用的协议。前面两个参数设定后,这儿可用0指定,表示缺省。


b. 返回值

成功返回新创建socket的文件描述符,失败返回-1


(2)设置socket信息

套接字编程——基于TCP协议_TCP_03

a. 成员

sin_:协议的地址类型;

sin_port:端口号,为16位的无符号短整型;

sin_addr:IP地址,为32位的无符号整型。


1> 网络字节序

1.      内存中的多字节数据相对于内存地址有大端小端之分

l        大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。

l        小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。

2.      磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。

3.      网络数据流也有大端小端之分:TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。

即网络数据流的地址这样规定:先发出的数据是低地址,后发出的数据是高地址。所以为了避免发送主机或接收主机是小端模式,需对数据做转换,下面的函数就实现了这一功能

套接字编程——基于TCP协议_socket_04


2>我们通常用点分十进制的字符串表示IP 地址,可以用下面的函数在字符串和in_addr之间转换。

套接字编程——基于TCP协议_socket_05


(3)绑定端口——bind()

套接字编程——基于TCP协议_TCP_06

a.   参数

sockfd:表示一个已经建立的socket编号(描述符);

addr:指向sockaddr结构体类型的指针;

addrlen:addr结构的长度,可以用sizeof函数获得。


b. 返回值

成功返回0,失败返回-1。


(4)监听客户请求——listen()

套接字编程——基于TCP协议_网络连接_07

a. 参数

sockfd:表示一个已经建立的socket编号(描述符);

backlog:连接请求队列的最大长度; 


b. 返回值

成功返回0,失败返回-1。


(5)接受客户端连接——accept()

套接字编程——基于TCP协议_socket_08

返回值: 成功返回接受的socket文件描述符,失败返回-1。


(6)连接服务器——connect()

套接字编程——基于TCP协议_网络连接_09

返回值:成功返回0,失败返回-1。


4. 数据传输的过程

  建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样:如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理。

  总结此过程:客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞 等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。

  如果客户端没有更多的请求了,就调用close() 关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。

  注意:任何一方调用close() 后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown() 则连接处于半关闭状态,仍可接收对方发来的数据。


5. 代码实现

//tcp_server.cpp
 1 #include<iostream>
  2 #include<string>
  3 #include<string.h>
  4 #include<stdlib.h>
  5 #include<sys/socket.h>
  6 #include<sys/types.h>
  7 #include<netinet/in.h>
  8 #include<arpa/inet.h>
  9 #include<errno.h>
 10 #include<pthread.h>
 11 
 12 using namespace std;
 13 const int g_backlog=5;
 14 
 15 void usage(string _proc)
 16 {   
 17     cout<<"Usage:"<<_proc<<"[ip][port]"<<endl;
 18 }
 19 static int startup(const string &ip,const int &port)
 20 {
 21     //1.创建套接字——socket()
 22     int sock=socket(AF_INET,SOCK_STREAM,0);
 23     if(sock<0)
 24     {
 25         cerr<<strerror(errno)<<endl;
 26         exit(1);
 27     }
 28     //2.设置socket
 29     struct sockaddr_in local;
 30     local.sin_family=AF_INET;
 31     local.sin_port=htons(port);
 32     local.sin_addr.s_addr=inet_addr(ip.c_str());
 33     //3.绑定
 34     if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
 35     {
 36         cerr<<strerror(errno)<<endl;
 37         exit(2);
 38     }
 39     //4.监听
 40     if(listen(sock,g_backlog)<0)
 41     {
 42         cerr<<strerror(errno)<<endl;
 43         exit(3);
 44     }
 45     return sock;
 46 }
 47 void *thread_run(void *arg)
 48 {
 49     int sock=(int)arg;
 50     char buf[1024];
 51     while(1)
 52     {
 53         memset(buf,'\0',sizeof(buf));
 54         ssize_t _size=read(sock,buf,sizeof(buf)-1);
 55         if(_size>0)
 56         {
 57             //read success
 58             buf[_size]='\0';
 59         }
 60         else if(_size==0)
 61         {
 62             //client close
 63             cout<<"client close..."<<endl;
 64             break;
 65         }
 66         else
 67         {
 68             cout<<strerror(errno)<<endl;
 69         }
 70         cout<<"client# "<<buf<<endl;
 71         }
 72     //close(sock);
 73     return NULL;
 74 }
 75 
 76 int main(int argc,char* argv[])
 77 {
 78     if(argc!=3)
 79     {
 80         usage(argv[0]);
 81         exit(1);
 82     }
 83     string ip=argv[1];
 84     int port=atoi(argv[2]);
 85     int listen_sock=startup(ip,port);
 86 
 87     struct sockaddr_in client;
 88     socklen_t len = sizeof(client);
 89     while(1)
 90     {
 91         int new_sock=accept(listen_sock,(struct sockaddr*)&client,&len);
 92         if(new_sock<0)
 93         {
 94             cerr<<strerror(errno)<<endl;
 95             continue;
 96         }
 97         cout<<"get a connect..."<<"sock:"<<new_sock\
 98             <<"ip:"<<inet_ntoa(client.sin_addr)<<"port:"\
 99             <<ntohs(client.sin_port)<<endl;
100 #ifdef _v1_
101         //version 1
102         char buf[1024];
103         while(1)
104         {
105             ssize_t _size=read(new_sock,buf,sizeof(buf)-1);
106             if(_size>0)
107             {
108                 //read success
109                 buf[_size]='\0';
110             }
111             else if(_size==0)
112             {
113                 //client close
114             }
115             else
116             {
117                 cout<<strerror(errno)<<endl;
118             }
119             cout<<"client# "<<buf<<endl;
120         }
121 #elif _v2_
122         cout<<"v2"<<endl;
123         pid_t id =fork();
124         if(id==0)
125         {
126             //child
127             std::string _client=inet_ntoa(client.sin_addr);
128             close(listen_sock);
129             char buf[1024];
130             while(1)
131             {
132                 memset(buf,'\0',sizeof(buf));
133                 ssize_t _size=read(new_sock,buf,sizeof(buf)-1);
134                 if(_size>0)
135                 {
136                     //read success
137                     buf[size]='\0';
138                 }
139                 else if(_size==0)
140                 {
141                     //client close
142                     cout<<_client<<"close..."<<endl;
143                     break;
144                 }
145                 else
146                 {
147                     cout<<strerror(errno)<<endl;
148                 }
149                 cout<<_client<<"# "<<buf<<endl;
150             }
151             close(new_sock);
152             exit(0);
153         }
154         else if(id>0)
155         {
156             close(new_sock);
157         }
158         else
159         {}
160 #elif _v3_
161         pthread_t tid;              
162         pthread_create(&tid,NULL,thread_run,(void*)new_sock);
163         pthread_detach(tid);
164 #else
165     cout<<"default"<<endl;
166 #endif
167 }
168 return 0;
169 }  

//tcp_client.cpp
  1 #include<iostream>
  2 #include<string>
  3 #include<string.h>
  4 #include<unistd.h>
  5 #include<stdlib.h>
  6 #include<sys/socket.h>
  7 #include<sys/types.h>
  8 #include<netinet/in.h>
  9 #include<arpa/inet.h>
 10 #include<errno.h>
 11 
 12 using namespace std;
 13 void usage(string _proc)
 14 {
 15     cout<<_proc<<"[remote ip] [remote port]"<<endl;
 16 }
 17 int main(int argc,char* argv[])
 18 {
 19     if(argc!=3)
 20     {
 21         usage(argv[0]);
 22         exit(1);
 23     }
 24 
 25     int r_port=atoi(argv[2]);
 26     string r_ip=argv[1];
 27 
 28     int sock=socket(AF_INET,SOCK_STREAM,0);
 29     if(sock<-1)
 30     {
 31         cout<<strerror(errno)<<endl;
 32         exit(1);
 33     }
 34 
 35     struct sockaddr_in remote;
 36     remote.sin_family=AF_INET;
 37     remote.sin_port=htons(r_port);
 38     remote.sin_addr.s_addr=inet_addr(r_ip.c_str());
 39 
 40     int ret = connect(sock,(struct sockaddr*)&remote,sizeof(remote));
 41     if(ret<0)
 42     {
 43         cout <<strerror(errno)<<endl;
 44     }
 45     string msg;
 46     while(1)
 47     {
 48         cout<<"please Enter:";
 49         cin>>msg;
 50         write(sock,msg.c_str(),msg.size());
 51     }
 52     return 0;
 53 }
 
 //makefile
  1 .PHONY:all
  2 all:tcp_client tcp_server
  3 tcp_client:tcp_client.cpp
  4     g++ -o $@ $^
  5 tcp_server:tcp_server.cpp
  6     g++ -o $@ $^ -lpthread -D_v3_
  7 .PHONY:clean
  8 clean:
  9     rm -f tcp_client tcp_server
  
  //statr.sh
  1 #!/bin/bash
  2 
  3 service iptables stop
  4 ./tcp_server 192.168.163.128 8080

输出结果:

server:

套接字编程——基于TCP协议_TCP_10

client:

套接字编程——基于TCP协议_网络通讯_11

[未完...关于2倍的MSL时间]