Approaching Networking |
GEOFF WOODCOCK: This is not a presentation
about Amway marketing. This is networking on the Be,
okay. How many people already know Berkeley sockets? If
you know that, you'll be a little bit bored in the
beginning, but I promise to give you something useful
before you leave. So this is our agenda. We're just
going to start out talking about -- step through some
code examples, Berkeley sockets API, what BeOS uses for
networking. There is no C++ object to do sockets, but
the DTS group has built one and we are working with the
networking engineer to improve them and you can find that
in the tutorial in the web site. So there is something,
but not part of code. BeOS. And then we're going to
finish off with some other advice for writing network
apps.
So first off, network protocol you can do UDP, TCP in the BeOS. UDP is a connectionless datagram socket. One slot type of thing. You find remote address, send to or receive from, and that's it. You don't get any error checking, any packet ordering, when the data is received. And if you want to know if the data actually got there, you have to implement that protocol on top of UDP. So you have to do a lot more work in UDP, but the advantage is it's faster and uses a lot less system resources.
I think what most people are probably
interested in is TCP, and that's a connection-oriented
network connection. It's like a phone connection. Once
I make the connection, you can just send and receive data
buffers. You don't really have to do a lot more than
that. But you pay for that ease because it uses a lot
more memory resources on the machine.
Whoops. Hey. Stop. Where was I? Okay.
Everybody probably knows what a client and a server is.
A server has many connections, many clients. A client
probably has one connection, one server. And there's a
little mantra you always go through in Berkeley sockets
whether you want to create a server or clients. In a
case of a server, you create a socket descriptor. Not
attached to anything. Then you have to bind it to a
local interface on machine and you call listen to tell
the net server you want to listen for client connections
on that socket, and then you have to loop. Every time
accept, refers it, refers a socket descriptor of a client
to you. Then you do send or receive, and when done with
a client, you call close sockets. Client, on the other
hand, create socket, call connect to connect to a remote
machine, and then do sending and receiving and close
socket.
So the first thing you want to worry about is how do you form the address to make the connection to the other side. And you do this a little differently, depending whether you're a client or a server. In the case of a client, you're probably going to get as input to make your address structure is the -- a string containing remote host name or IP address and a port. So there's ports for http, all that stuff.
So it's just a number. The way you convert
that into this address structure is gethostbyname. That
you can pass either the host name or a string containing
the IP address, return port and structure. Once you get
that, you can fill in this socket structure. You can see
sin family is AF INET, always. It is the only thing that
it supports. Another thing is this H2NS call on the sin
port. That's just converting host to network short, so
that gives you supportability. And then the last thing
is actually putting in the binary representing the remote
address into the structure.
By the way, I don't think I mentioned this, but
we're focusing on TCP on all this stuff. So after you've
made the address or in addition to that, you want to
actually make a socket that is connected to that address.
So in the case of a client, you're going to do the
socket call, which you see on the top, and the type there
is the part that specifies TCP or UDP. So in most cases,
proto underscore TCP or something. And let's see. Where
are we at? You make the sockaddr call. You make the
call to actually connect to this remote machine. So it's
not going to return if the connect attempt fails or you
get disconnected from the machine. This is the first
example of something you might want to put in a separate
thread so the rest of your application doesn't slow
down.
Now, in the case of a server, you're not connecting to a remote machine. You're connected to a local net interface, ethernet card or PPP or something. So what you get there is, again, you have to pass the port to specify the service you're going to be serving, then you're going to pass them an address. There's some constants you can see here. Basically, you're going to use give me the network interface. But you can use INADDR LOOPBACK because LOOPBACK always works. 127.0.0.1.
There is some discussion in the DR8 BeBook, and
Brad Taylor, the networking engineer, is here. He can
probably answer the question. Again, you see. MMS -- by
the way, a lot of this stuff you get it down and it never
changes. This isn't the kind of code where you're going
to have to go and alter very often. Okay. So the server
does things a little differently. He does the MakeSock
to connect to and then he does a bind call, which does
just what it says, it binds this abstract socket to this
address.
Once you have done that, you have to make a
listen call to tell the net server, "I'm ready to start
listening for clients to attempt to connect on this
socket." So that's the difference between doing it with
the client, doing it with the server. In the case of the
client, you're creating the socket you're sending and
receiving. In the case of the server, the first socket
you create is the one that the clients are going to
connect via.
So again, with the server, once you've got this
server socket, you have to create a loop that accepts
client connections and handles them. This is another
blocking function which you're probably going to want to
put in a separate threading. And you can just see that
down here. You make a call to accept. If accept returns
greater than or equal to zero, what you get in the return
value is the socket descriptor on the local machine of
the client connection. You don't have to do anything
else with it. That socket descriptor is valid to return
once it's returned from accept. So you're probably going
to make a call to start handling this new client
connection.
Okay. So sending and receiving is the same for
clients and servers. And there is no problem with this
example I'll get to in a minute. But basically you're
sending in a socket descriptor. And sending, you want to
send, and it can get a little tricky because even though
TCP is reliable, sockets can get interrupted for
different reasons. There can be times when send or
receive doesn't send all the data, so that's what this
loop is for, just looping making sure you send all the
data. And we'll talk about why sockets get interrupted
in a minute. But for now, don't let that bother you.
So receiving is a little different. It's asynchronous and you know exactly what's going to happen. You're calling send. In the case of receiving data, you don't know when the other guy on the other end is going to want to send you data, so you have to do some other stuff up front to handle that. So the first interesting structure is this FD set and we have rdBits and wrBits. If you do, you know what it is. One or more sockets. It says if you don't get any more data in this amount of time, return anyway.
And the FD set structure, what you do with that in the case of rdBits is you use this FD set macro to add sockets to this rdBit structure. As you do that, you're saying, I want to look at this socket, I want to look at this socket. And once you've added all those sockets to this rdBits, then you can call select, so select will get called. The first parameter specifies the range. In this case we're only worried about one socket, so we make one call to FD set, and for the range, socket ID plus one. So that call is going to block and it will return either when data is received or when the timeout is reached.
There's one important thing which I'll probably point out a couple times, but I'll start here. That is you probably should not put more than one socket in a select call. The reason for that is that the -- when you put more than one socket in a select call, the net server is going to spawn multiple threads to wait for the sockets. When it returns, it's going to destroy it. You're looping and waiting for data, handling, and you don't want all those threads being destroyed when you go through the loop. So a far superior solution is create one thread for each socket when you get the new socket connection and have each one put one socket in select in its data waiting loop.
So anyway, I skipped one thing on this slide. So in select returns, in this case, we know that data arrived on that single socket. There are other things you can do if for some reason you have to do multiple sockets. In this case we know it's on that socket. So make a call to receive data. It actually received the data. And here's received data.
So here again we're doing some looping to handle interrupts. And I don't think I pointed this out on the last side. EINTR is what you get when your socket gets interrupted. You already know because the select data is there. In many cases you won't know how much data arrived, so you have to alter this -- okay. This is a little hard to explain. If you don't know how much data is going to arrive, what you have to do in a lot of cases is set the socket to nonblock. When you set the socket to nonblocking, this receive call will always return. If you set the socket to blocking, the receive will block until data arrives. That's a problem if you don't know how much data is going to arrive because you might call received after you've already gotten all the data that's waiting and then it's going to block.
If you make the socket nonblocking, when you make a call to receive when no data is waiting, it will return the error value, "E would block." You can use that as a flag to say, "I have all the data waiting." You can modify this function to return the actual number of bytes received as opposed to the number -- this size of the buffer. So that's one alteration you might have to make to function like this, to make it more useful.
And receive should never return exactly zero unless the other side has gone away. So maybe it went away normally, but in any case, it will return zero. In this case, I just say if the other end disconnected while I was in the middle of receive, I consider that ECONNABORTED, the same, giving you the socket error value. Okay.
It seems to have stopped. Sorry about this. I don't know why it stopped.
Okay. So we mentioned earlier a lot of the network calls do block and there is a socket option that you can -- there's a function called setsockopt. And the only option we support in setsockopt is setting whether you want the socket blocking or not. If you set nonblocking, it will return immediately. If it would have blocked, if the socket was set to block, the return, "E would block." If you set it blocking it's not going to return until it's complete or a timeout or some other error.
And if it's a blocking socket and you have multiple threads operating on that socket at the same time, they're going to interrupt each other. Only one activity can be going on on a socket at a time. If you've got this multi-turning obligation, you want some type of mechanism for keeping two things going on at the same time, like sending and receiving at the same time.
Okay. Now, there may be times when you want to specifically interrupt a socket. When the application exists, you might want to interrupt all the block functions so the threads exist. There's two ways to do this. One way, with signals. Another UNIX concept, in the BeOS. You can get the thread ID, keep track of it up front, blocked on the network functions and you can send them a SIGINT signal, as return.
The other way you do this, what everybody did
in DR8, we didn't have signals, you just call a function
on a socket and that will interrupt a socket. You keep
track of the socket descriptors. You can call
getsockname, the safest one to use, and that will
interrupt all the sockets.
So as we said earlier, we support the Berkeley
sockets API to a certain degree. Of course, TCP and UDP
support and all the network calls we talked about
before. As far as setsockopt goes, for those of you
familiar with, and the only one you can set is nonblock.
As far as differences between us and standard
Berkeley, currently we don't support file descriptors.
You can wait for terminal and data to arrive from a
network. And you can use file type functions like iotc
close. This is capability, important stuff. We don't
support multi-tasking. We don't support any of the less
commonly used socket options like keep live, broadcast,
linger data, but, sizing, socket options. And also in
select there's something called out of band data, bits.
We don't do that. And we don't. And if you don't know
what they are, don't worry about them because you
probably won't use them.
To summarize, one socket for select. Do that. Use threads for blocking functions. Make sure you're not doing more than one thing on a socket descriptor at one time because you're going to interrupt and be inefficient.
As far as documentation, no new DR9 network. But we have the Berkeley API in DR8. The information isn't complete. It doesn't include all features added in DR9, but what is there is connect. You can look at that. There's also some networking samples on the web site, which we will be turning out in a DR9 version next week, so you can also look at that. That's where we have the C++ socket classes that we do use.
So we've got plenty of time for questions and Brad is in the building. He came specifically so he could answer your questions.
Yes, sir?
A SPEAKER: The architecture, are you going to talk about that -- the stack?
GEOFF WOODCOCK: I think Brad would probably be better to comment about that. He left a mike here for you. Let me grab it. A little Karaoke here. Try this one. It's easier.
A SPEAKER: What is the direction of networking and BeOS?
BRAD TAYLOR: So the question was direction of the networking stuff and then BeOS in the future, and what were the other issues?
A SPEAKER: Performance, DR9, enhancements, things like that.
GEOFF WOODCOCK: Okay.
BRAD TAYLOR: I'll talk about DR9 first. Does this one work? Yeah, DR9, the -- we improved the performance of TCP and you'll find that for like a BeBox, using an ISA card you can get 600 megabytes a second reception speed. For a Mac or PCI card on a BeBox you can go from 1 megabyte a second reception.
On send, it's not quite up to what I would like it. It's not a megabyte, but like 900 K a second, I believe. The other things we've done with DR9 I think Geoff mentioned already. Signals work with sockets, so if you're porting Berkeley programs depending on that, it won't screw things up like in DR8.
I guess the blocking in send is new. In DR8 you couldn't do nonblocking sends and now that works. Select supports. More than 312 sockets in DR9.
GEOFF WOODCOCK: The wrBits.
BRAD TAYLOR: That's the nonblocking. I guess that's it.
I guess the other part is where we're going in the future. I think the main thing you're going to see in future releases is sort of completing more the Berkeley stuff, getting -- possibly having sockets be file descriptors, doing more of the socket options. And in general, a lot of performance improvement. Still a long way to go. I got like the reception speed very good now. I would like to get a lot of the other stuff going really fast. Does that answer your question?
A SPEAKER: What about support for AppleTalk printing?
BRAD TAYLOR: We do have limited support for AppleTalk printing. Any other protocol support other than IP version 6 would be a third party opportunity. But as IP version 6 becomes more real --
A SPEAKER: That wasn't working properly.
BRAD TAYLOR: Another thing, INET should work. On DR8 you have it tied specifically. I'm not sure if that works totally correctly with the advanced access. But DR9 in the end, it will work.
A SPEAKER: The operating systems -- multi and more and more -- are protocols a high priority for you?
BRAD TAYLOR: That's sort of more completing the Berkeley functionality that will be an ongoing project.
A SPEAKER: Do you think by the time of the preview?
BRAD TAYLOR: I don't believe it's going to make it to the preview. No. Sorry about that.
A SPEAKER: For somebody whose day job is writing large multi-player game servers on other platforms, I'm interested in components issues with having a server with several thousands of connections. Specifically, is there any performance problem that you see of having 5,000 threads per socket instead of each having their own stack? Would it be better to go like a thread pool model?
BRAD TAYLOR: I think the best way to do that, as Geoff mentioned, a little bit like TCP having a little more memory overhead. If you use TCP, there's going to be a thread in every socket and in your application probably for every socket unless you want to use select, which would be very inefficient. So I would recommend if you're using that kind of game to use UDP. You have one socket to receive data from any client.
A SPEAKER: Unfortunately, there's the security issue.
BRAD TAYLOR: Well, they should fix that. It's no worse than TCP as far as security goes.
A SPEAKER: A related question, what would be the practical method for a server for the number of fonts for a server to support and maybe about how much we should allow per font for service support for TCP?
BRAD TAYLOR: For TCP, I would say there's probably going to be two sockets for every TCP connection, one net server and one every application. Each one gets a stack. Probably the biggest amount of memory used. Stacks in DR9 or 100 and --
A SPEAKER: What about practical performance?
BRAD TAYLOR: I'm assuming you have a large amount of memory and --
A SPEAKER: Well --
BRAD TAYLOR: -- there is a limit of 65,000 on the number of ports you can have, but that's probably an impractical limit. I suspect you could handle hundreds, hundreds of clients.
A SPEAKER: You just said the stack size is now 128 K.
BRAD TAYLOR: Yes.
A SPEAKER: One of the newsletters said 64 K.
BRAD TAYLOR: Unless they changed it back, the last I knew was 128 K.
Anybody over here have a question?
GEOFF WOODCOCK: We can maybe repeat that question in the final session and get the definite answer.
A SPEAKER: Do you support any other networking protocols?
BRAD TAYLOR: Right now our drivers have PPP, the on board Mac ethernet, the DEC 10 megabit, DEC megabit card. And for the BeBox. NT, 3Com, too. As far as the other media go, I think our current plans are to hope for third parties to work. That there's a lot of interested parties doing a hundred megabit, so I suspect you'll see that very soon. I haven't heard of anything, anybody doing the giga. There is one person doing --
GEOFF WOODCOCK: That's all.
A SPEAKER: You mentioned PPP. Does this work better now? I remember DR8, it was a total disaster.
BRAD TAYLOR: It's a lot better in DR9. Mainly the configuration is better. We have a list of hundreds of modems on string. Hopefully, most of the modems are listed in there. It's a lot more friendly. It's a lot faster, too.
A SPEAKER: The server BeOS, what are the connections?
BRAD TAYLOR: A lot depends on the hardware you're running on. I think on my Mac I'm getting about maybe about 30 connections a second. I would like it to be -- that to be a lot higher, and I'm working on that.
A SPEAKER: What type of Mac do you have?
BRAD TAYLOR: Power PC, 120, 604.
A SPEAKER: Does your modem connect? Because the original one is through net positive, not PPP. I can't download anything.
GEOFF WOODCOCK: You're saying PPP?
A SPEAKER: The modem connects to the direct terminal.
BRAD TAYLOR: You're talking about direct serial link?
A SPEAKER: Yeah.
BRAD TAYLOR: The last I know, that works pretty well with DR9. It works on 115, too.
A SPEAKER: I know you guys don't have an underlying -- can you talk about it, encrypting level kinds of things -- stack?
BRAD TAYLOR: There was a talk I gave yesterday about network drivers for DR9. One of the things you can do is install things called packet handlers that are normally used for protocols. A packet handler looks at packets coming over ethernet. You can also throw in there a packet sniffer which actually doesn't eat the packet. Just sets its priority high so it gets the packet first. You can look at it, do whatever you want with it. Select statistics. That should be possible in DR9. Although, with the advanced access copy you have no way to turn on promiscuous mode. No API hook for that. Probably in final DR9, support.
Anybody else? Okay. We're done.
GEOFF WOODCOCK: Yep.
Home | About Be | Be Products | BeWare | Purchase | Events | Developers | Be User Groups | Support