基础知识
(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()返回。
3. 相关函数
(1)创建套接字——socket()
a. 参数
domain:表示底层通信所使用的协议,有很多选项。这儿我们选择AF_INET格式,IPv4网络协议;
type:表示协议实现的方式,也有很多选项。这儿我们用SOCK_STREAM,它 提供有序的、可靠的、双向的和基于连接的字节流,使用带外数据传送机制,为Internet地址族使用TCP。 (在UDP中,我们使用参数SOCK_DGRAM ,它支持无连接的、不可靠的和使用固定大小(通常很小)缓冲区的数据报服务);
protocol:套接口所用的协议。前面两个参数设定后,这儿可用0指定,表示缺省。
b. 返回值
成功返回新创建socket的文件描述符,失败返回-1
(2)设置socket信息
a. 成员
sin_:协议的地址类型;
sin_port:端口号,为16位的无符号短整型;
sin_addr:IP地址,为32位的无符号整型。
1> 网络字节序
1. 内存中的多字节数据相对于内存地址有大端小端之分
l 大端模式(Big_endian):字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中。
l 小端模式(Little_endian):字数据的高字节存储在高地址中,而字数据的低字节则存放在低地址中。
2. 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分。
3. 网络数据流也有大端小端之分:TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
即网络数据流的地址这样规定:先发出的数据是低地址,后发出的数据是高地址。所以为了避免发送主机或接收主机是小端模式,需对数据做转换,下面的函数就实现了这一功能
2>我们通常用点分十进制的字符串表示IP 地址,可以用下面的函数在字符串和in_addr之间转换。
(3)绑定端口——bind()
a. 参数
sockfd:表示一个已经建立的socket编号(描述符);
addr:指向sockaddr结构体类型的指针;
addrlen:addr结构的长度,可以用sizeof函数获得。
b. 返回值
成功返回0,失败返回-1。
(4)监听客户请求——listen()
a. 参数
sockfd:表示一个已经建立的socket编号(描述符);
backlog:连接请求队列的最大长度;
b. 返回值
成功返回0,失败返回-1。
(5)接受客户端连接——accept()
返回值: 成功返回接受的socket文件描述符,失败返回-1。
(6)连接服务器——connect()
返回值:成功返回0,失败返回-1。
4. 数据传输的过程
建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样:如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理。
总结此过程:客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞 等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。
如果客户端没有更多的请求了,就调用close() 关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。
注意:任何一方调用close() 后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown() 则连接处于半关闭状态,仍可接收对方发来的数据。
5. 代码实现
//tcp_server.cpp 1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 12 using namespace std; 13 const int g_backlog=5; 14 15 void usage(string _proc) 16 { 17 cout<<"Usage:"<<_proc<<"[ip][port]"< 0) 56 { 57 //read success 58 buf[_size]='\0'; 59 } 60 else if(_size==0) 61 { 62 //client close 63 cout<<"client close..."< < < <<"client# "< < 0)107 {108 //read success109 buf[_size]='\0';110 }111 else if(_size==0)112 {113 //client close114 }115 else116 {117 cout< < <<"client# "< < <<"v2"< 0)135 {136 //read success137 buf[size]='\0';138 }139 else if(_size==0)140 {141 //client close142 cout<<_client<<"close..."< < < <<_client<<"# "< < 0)155 {156 close(new_sock);157 }158 else159 {}160 #elif _v3_161 pthread_t tid; 162 pthread_create(&tid,NULL,thread_run,(void*)new_sock);163 pthread_detach(tid);164 #else165 cout<<"default"< 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 12 using namespace std; 13 void usage(string _proc) 14 { 15 cout<<_proc<<"[remote ip] [remote port]"< <-1) 30 { 31 cout< < <0) 42 { 43 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:
client:
[未完...关于2倍的MSL时间]