设为首页 收藏本站
查看: 1307|回复: 0

[经验分享] 基于TCP协议下的socket编程

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2016-5-24 11:07:32 | 显示全部楼层 |阅读模式
socket:
  TCP/IP协议中一个端口号和一个IP地址绑定在一起就生成一个socket就表示了网络中唯一的一个进程,它是全双工的工作方式。

基于TCP的socket编程
函数的使用:
1、socket()
1
2
3
4
        #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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



socket它用于创建一个套接字,返回值是一个文件描述符。
domin表示该套接字网络层所用的协议,因为我们用的是iPv4协议,所以这里填AF_INET。
type表示该套接字传输层使用什么模式传输,TCP是字节流的,所以这里填SOCK_STREAM。
protocol填写为0,系统会根据你之前的参数把它设置为相应字段,比如我们参数设置为AF_INET,SOCK_STREAM,0,后面的0会自动设置为IPPROTO_TCP。
2、bind()
1
2
3
4
5
  #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);



sockfd就是套接字
struct sockaddr:首先需要认识下面两个结构体struct sockaddr_in 和struct sockaddr_un。
wKiom1dBqTui0JP1AADSMoaMXxM793.jpg
通常情况下习惯于用sockaddr_in结构体来填充ip和端口号字段然后强转为struct sockaddr。
1
2
3
4
5
6
7
8
9
10
11
12
13
struct sockaddr_in

{

short sin_family;/*Address family一般来说AF_INET(地址族)PF_INET(协议族)*/

unsigned short sin_port;/*Port number(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/

struct in_addr sin_addr; 使用inet_addr(),将字符串ip转化为网络数据的格式。

unsigned char sin_zero[8];/*Same size as struct sockaddr没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐*/

};



3、网络字节序:
一台主机有大小端模式而在网络中传输数据的时候需要遵循一定的规则,即发送端从地地址开始发送,接收端从低地址开始接受,如果两台主机的大小端模式不一样,那么就会导致数据的错乱,tcp/ip协议中规定网络数据流应该采用大段的字节序,即高地址高位。为了方便就有了一系列转化的函数方便使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
      #include <arpa/inet.h>

       uint32_t htonl(uint32_t hostlong);

       uint16_t htons(uint16_t hostshort);

       uint32_t ntohl(uint32_t netlong);

       uint16_t ntohs(uint16_t netshort);

       //上面这些函数用于将端口号转化为大端模式
       //下面的多用于将ip地址字符串转化为大端模式

        #include <sys/socket.h>
       #include <netinet/in.h>
       #include <arpa/inet.h>

       int inet_aton(const char *cp, struct in_addr *inp);

       in_addr_t inet_addr(const char *cp);

       in_addr_t inet_network(const char *cp);

       char *inet_ntoa(struct in_addr in);

       struct in_addr inet_makeaddr(int net, int host);

       in_addr_t inet_lnaof(struct in_addr in);

       in_addr_t inet_netof(struct in_addr in);



4、listen()
1
2
3
4
   #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);



listen的作用主要是用来将当前已经绑定的套接字设置为监听状态,保持一个LISTEN的状态
backlog的作用是用来设置当前最多能允许多少远端套接字在监听队列中排队。
5、accept()
1
2
3
4
  #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);



accept的作用是将监听到的远端套接字accept到然后返回一个新的文件操作符,可以想象,如果不停的listen到则会有很多的文件操作符返回,再用一些方法便可以实现一对多的链接了,回到前面一个博客的问题,这时候如果大量远端套接字被accept到,然后服务器主动断开链接,大量的TIME_WAIT状态就是在这个时候产生的,(解决方法见上一篇博客)。

函数中的addr和addrlen即时输入型参数也是输出型参数,因为我们要获取远端的套接字信息
6、connect()
1
2
3
4
5
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);



很显然这是一个客户端需要的一个函数,它用来链接远端的服务器。

下面是实现的代码:
分了三种形式来实现,一对一,一对多(多进程)和一对多(多线程)。


服务器端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
  1 #include<iostream>
  2 #include<sys/socket.h>
  3 #include<sys/types.h>
  4 #include<arpa/inet.h>
  5 #include<string.h>
  6 #include<string>
  7 #include<errno.h>
  8 #include<stdlib.h>
  9 #include<stdio.h>
10 #include<sys/wait.h>
11 #include<signal.h>
12 #include<pthread.h>
13
14 using namespace std;
15
16 //下面是在多进程下注册的一个信号处理函数,能使得父进程不用
     //再阻塞式或者轮询式的等待子进程的退出,当有子进程退出时会
     //发送SIGCHLD信号我们捕捉这个信号后对其回收。
17 void hander(int sign)
18 {
19     int status=0;
20     while(waitpid(-1,&status,WNOHANG)>0)
21     {
22         int cur1=(status>>8)&0xff;//取低八位的信号
23         int cur2=status&0xff;//取高八位的退出码
24         cout<<"a child leave.."<<"signal::"<<cur2<<"code::"<<cur1<<endl;
25     }
26 }
27
28 int Listen(string &ip,int port)
29 {
30     int sock=socket(AF_INET,SOCK_STREAM,0);//创建一个套接字
31     cout<<sock<<endl;
32     struct sockaddr_in server;
33     server.sin_family=AF_INET;
34     server.sin_port=htons(port);
35     server.sin_addr.s_addr=inet_addr(ip.c_str());
36     int bindret=bind(sock,(struct sockaddr*)&server,sizeof(server));
                //绑定到端口上,否则操作系统会分配一个随机端口。
37     if(bindret<0)
38     {
39         cout<<strerror(errno)<<endl;
40     }
41     int listen_sock=listen(sock,10);
                 //设置监听状态
42     if(listen_sock<0)
43     {
44         cout<<strerror(errno)<<endl;
45         exit(2);
46     }
47     return sock;
48 }
49
50 void* output(void*output_sock)
         //一个读端的线程,设置这个线程的目的是为了
         //让主线程不停地accept而不停地使用这个线程
         //来读数据
51 {
52     int sock=(int)output_sock;
53     char buf[1024];
54     while(1)
55     {
56         ssize_t _size=read(sock,buf,sizeof(buf)-1);
57         if(_size>0){
58                 buf[_size]='\0';
59         }else if(_size<0){
60                 cout<<strerror(errno)<<endl;
61         }else{
62             cout<<"leave..."<<endl;
63             break;
64         }
65                 cout<<"client ::"<<buf;
66                 fflush(stdout);
67     }
68 }
69
70 int main(int argc,char *argv[])
71 {
72     if(argc!=3) //命令行参数格式设置为 ./server ip port的格式
73     {
74         cout<<"please output like this"<<"argv[0]" <<"ip"<<"port";
75         exit(1);
76     }
77     string ip(argv[1]);
78     int port = atoi(argv[2]);
79     int listen_sock=Listen(ip,port);
80     struct sockaddr_in client; //这个client的作用是为了获取远端的套接字信息
81     socklen_t len=sizeof(client);
82     while(1)
83     {
84        int output_sock=accept(listen_sock,(struct sockaddr*)&client,&len);
                    //从监听队列中获取一个远端套接字信息,并新生成一个文件描述符。
85        if(output_sock<0)
86        {
87            continue;
88        }
89        char buf[1024];
90        cout<<"get connect..."<<"ip::"<<inet_ntoa(client.sin_addr)<<"port\
91                ::"<<ntohs(client.sin_port)<<endl;
92
93    #ifdef _TEST1_ //条件编译(在Makefile中gcc -o $@ $^ 后面加上-D_TEST1_即可完                             //       成条件编译)
                           //TEST1完成的是一对一。这种模式显然有很大的缺点。只能满足一个                            //用户的服务器显然没有什么用处
94         //one connect
95         cout<<"TEST1"<<endl;
96         while(1)
97         {
98             memset(buf,'\0',sizeof(buf));
99             ssize_t _size=read(output_sock,buf,sizeof(buf)-1);
100             if(_size>0)
101             {
102                 buf[_size]='\0';
103             }else if(_size==0){
104            cout<<"leave..."<<"ip::"<<inet_ntoa(client.sin_addrr)<<endl;
105                     break;
106             }else{
107
108                 cout<<strerror(errno)<<endl;
109             }
110             cout<<"client::"<<buf;
111         }
112   #elif _TEST2_
                  //TEST2实现的是一个多进程的一对多的服务器,虽然解决了多人的链接问题,                    //但是进程所占用的资源是很大的。        
113       // fork
114       cout<<"TEST2"<<endl;
115       pid_t id=fork()
116       if(id==0){
117           close(listen_sock);
                           //子进程不需要listen_sock。只需要读数据就行了。
118           while(1)
119           {
120               ssize _size=read(output_sock,buf,sizeof(buf)-1);
121               if(_size>0)
122               {
123                  buf[_size]='\0';
124               }else if(_size ==0){
125                 cout<<"leave..."<<"ip::"<<inet_ntoa(client.sin_addr)<<end
126                     break;
127                 }else{
128                     cout<<strerror(errno)<<endl;
129                 }
130             }
131             close(output_sock);
132         }else if(id<0){
133             cout<<strerror(errno)<<endl;
134         }else{
                              //父进程只需要关心listen_sock;
135             close(output_sock);
136             signal(SIGCHLD,hander);
137         }
138     #elif _TEST3_
                      //实现的是一个多线程一对多的服务器,同样线程也有开销。
139         //pthread
140         cout<<"TEST3"<<endl;
141         pthread_t id;
142         pthread_create(&id,NULL,output,(void*)output_sock);
143         pthread_detach(id);
144     #else
145     cout<<"default"<<endl;
146     #endif
147 }
148     return 0;
149 }



(几处地方复制过来缩进有点问题,注释时复制过来后来添加的)。

客户端
客户端需要一个套接字,因为服务器需要知道你的信息,但他不需要绑定,它只关心链接到服务器端即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  1 #include<iostream>
  2 #include<sys/socket.h>
  3 #include<sys/types.h>
  4 #include<arpa/inet.h>
  5 #include<string.h>
  6 #include<string>
  7 #include<errno.h>
  8 #include<stdlib.h>
  9 #include<sys/wait.h>
10 #include<signal.h>
11 #include<pthread.h>
12 using namespace std;
13
14 int main(int argc,char*argv[])
15 {
16     if(argc!=3)
17     {
18         cout<<"please input::"<<"ip[]"<<"port[]"<<endl;
19     }   
20     int port=atoi(argv[2]);
21     const string server_ip=argv[1];
22     int sock=socket(AF_INET,SOCK_STREAM,0);
23     if(sock==0)
24     {
25         cout<<strerror(errno)<<endl;
26         exit(1);
27     }   
28     struct sockaddr_in client;
29     client.sin_family=AF_INET;
30     client.sin_port=htons(port);
31     client.sin_addr.s_addr=inet_addr(server_ip.c_str());
32     int consock=connect(sock,(struct sockaddr*)&client,sizeof(sockaddr_in));
         //链接到目的套接字,并把自己的套接字信息发送给对方
33     if(consock<0)
34     {
35         cout<<strerror(errno)<<endl;
36     }
37     string msg;
38     while(1)
39     {
40         cout<<"please write msg"<<endl;
41         cin>>msg;
42         write(sock,msg.c_str(),msg.size());
43     }
44     return 0;
45 }
46




总结:虽然实现了一个一对多的服务器,但是弊端是很容易发现的,首先,不管是多线程还是多进程的服务器,它的资源消耗是很大的,再说说多线程和多进程的缺点,当连接数目非常大的时候,cpu的调度也会变的很麻烦,虽然说cpu的执行速度非常快,但当成千上万个进程或者线程在运行的时候它也是会吃不消的。


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-221093-1-1.html 上篇帖子: linux下date命令得到今天日期的用法 下篇帖子: PXE和Cobble实现自动装机
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表